D++ 是 我 (K歌之王) 设计 的 一个 计算机编程 语言 。
D++ 的 共同设计人 : 馥岚过野 , :| , 左边 , 这是 3 个 网友 的 网名 。 他们 提供了 丰富 的 意见 和 经验, 涉及 软件 , 硬件 , CPU 架构 , 现有的 语言 和 GC , 程序架构 和 模型 , 业界新闻 …… 我们展开了 热烈 和 卓有成效 的 讨论, 我从 他们 那里 获取了 许多 灵感 。
D++ 是 ILBC 的 一个 子项目, 有关 ILBC ,见 《ILBC 白皮书》 https://www.cnblogs.com/KSongKing/p/11070978.html 。
D++ 是 一个 通用 的 计算机语言, 可以用于 底层编程,也可以用于 业务编程 。 从名字上可以看出来, 既然 叫 D++ , 当然 希望 能够 做 C++ 做的 一些 事情 , 因此, D++ 也偏 底层编程 一些 , 用 D++ 写程序会 考虑 程序 本身 的 “技术性” 多一点, 而 业务编程 的 话, 希望 最好 不要 程序员 考虑 程序 本身 的 “技术性” 。
了解 D++ ,可以加入 QQ 群 K 开源联盟 , 群号 1004739167 。
其实 一开始 搞 的 是 D# , 后来发现 把 底层编程 和 业务编程 两个 需求 合在一个 语言 里 会让 语言 膨胀 变得 庞大, 于是 就 分开, 底层编程 是 D++ , 业务编程 可以用 类似 javascript 的 “高性能脚本语言” , 可以比 javascript 简化 。也可以 搞 功能全面 的 D# 。
高性能脚本语言 和 D# 都以 D++ 作为 中间语言 来 实现 。
D++ 和 D# 的 语法 和 C# 差不多,就不多说了 。
D++ 有 类 、继承 , 没有 虚函数多态 。 D# 有 虚函数多态,比如 虚方法 接口 抽象方法 抽象类 等 , 还可以有 Lambda 表达式 等 。
用 D++ 作为 中间语言 的 话, 怎么 来 实现 D# 的 虚函数多态 呢 ? 在 C++ 和 C# 里, 引用 是 一个 结构体, 如果 是用 D++ 作为 中间语言 的 话, 可以 让 D# 的 引用 是 D++ 里 的 一个 池对象, 池 就是 内存池 Pool 。
D++ 由 Heap , Pool , Bag 3 个 部分 组成 。
Heap 是 CPU 多核共享的, Heap 里 的 对象 称为 堆对象 。 严格的说, Heap 是 多核共享 是指 多核 可以到 一个 堆 里 申请 空间, 且 一个 核 运行 的 程序 申请 的 空间 可以 在 别的 核 运行 的 程序 里 归还 堆 。
至于 空间 里 的 数据 是否 多核共享,这个 不一定, 如果要 多核共享, 需要 使用 比如 原子类型 。
也可以 搞 一核一堆 , 但在 核 0 的 堆 里 申请 的 空间, 可以在 其它 核 比如 核 1 运行 的 程序 里 归还 堆, 这个 堆 当然 是 核 0 的 堆, 这也是 多核共享 Heap 。
Heap 分为 const 区 和 Grid 区 , const 区 只分配不回收, const 区 里 的 对象/数据 创建以后 只要 应用程序 不结束 会 一直存在 。
Grid 区 也称为 Grid Heap ,
Grid Heap 的 思想 也可以称为 “碎片隔离” 。 图中 的 “一格” 是 1 MB , 也就是 1 个 Grid 是 1 MB , 也就是 Grid Size 是 1 MB 。
可以把 Grid Size 分为 几种 不同 大小 的 规格,比如 8 KB , 128 KB , 1 MB 。
如果 Heap 从 操作系统 取得的 “大块空间”(div) 是 1 MB , 可以分配 128 个 8 KB Grid, 8 个 128 KB Grid 。
Pool 是 内存池, 是 单核的, 同一时间 只能 运行在 一个 线程 上, 在 不同的时间, Pool 可能 运行在 不同的 线程,但这些 线程 都是 同一个 核 的, 因为 Pool 里 的 数据 是 普通类型, 不是 原子类型, 不支持 严格的 多核同步数据 。
要 多核同步数据, 也就是 多核共享数据, 要用 堆对象, 并且 在 堆对象 里 使用 原子类型 。
Pool 使用时 要 lock 和 绑定 到 线程, 使用完 时 unlock 和 解除 绑定, 绑定 到 线程 可以 指定一个 寄存器 存放 池对象分配指针, 这样 可以 一个 时钟周期 new 一个 池对象 。
一个 Request 对应 一个 Pool , Request 就是 执行一段代码, 让 程序 做 一些 工作 。 也可以说 Request 是 D++ 里 的 程序 的 基本单位 。 但还有 比 Request 更小 的 单位,是 任务(Task) 。
如果 没有 IO 异步 , 一个 Request 就是 一个 Task, 如果 有 IO 异步, 比如 有 一次 IO 异步 , 一个 Request 会被 分解 为 2 个 Task , 一个 Task 是 执行 IO 异步 前 的 代码, 第二个 Task 是 执行 IO 异步 的 回调 。 如果 有 多次 IO 异步, 一个 Request 会被 分解 为 多个 Task 。
这些 Task 是 串行 的, 串行 的 使用 Pool , 所以也叫做 串行任务,串行 Task 。
所以, 上面说, Pool 使用时 要 lock , 就是为了 确保 IO 异步 完成时, 回调 Task 们 串行 的 执行, 也就是 串行 的 使用 Pool , 而不是 并发 的 执行,并发 的 使用 Pool 。
Bag 就是 把 一些 数据 / 对象 封装在 一个 对象 里, 对外 提供一些 操作 这些 数据 / 对象 的 方法 , 这个 对象 称为 Bag 。 其实 Bag 只是一个 习惯 的 称呼, 并不是 语法 。
比如, 可以把 一些 对象图 或者 数据 封装为 一个 Bag, 比如 二叉树 , 数据库 的 表 。
Bag 和 外界 通过 值传递 传递数据, 也就是说, Bag 的 方法 的 参数 和 返回值 都是 值类型, 比如 int, float, double …… 值类型数组 , 结构体 。
为了 提高效率, 不需要 每次 读写 Bag 里 的 数据 都要 把 数据 复制出来 又 复制进去, Bag 可以返回一个 类似 C# 里的 Span<T> 的 东西,这个 东西 实际上 是一个 结构体,在 D++ 里 称为 Local Agent , 在 语法 上, 需要 Bag 是 readonly 变量, 才能 返回 Local Agent, D++ 也可能 需要 提供一些 语法支持 让 Local Agent 有一个 作用域,以及 使用完(作用域结束)时 可以调用 一个 类似 Dispose() 的 方法 。
Bag 是 堆对象, 是 堆对象 的 一种, 上面也说了, Bag 只是一个 习惯性 的 称谓, 并不是 严格 的 定义,也不是 语法 。
所有 的 堆对象 都是 native class , native class 就是用 InnerC 编写 的 class , D++ 没有 编写 堆对象 的 语法 。
D++ 提供了 编写 native class 的 语法, 类似一个 接口 定义, 定义出 native class 的 成员(方法), 包括 这些 成员(方法) 的 签名 。
D++ 编译器 会 把 接口定义 翻译成 InnerC 的 表达, 程序员 去 编写 具体 的 InnerC 的 代码实现, InnerC 编译器 会 把 接口 和 实现 的 InnerC 代码 放到一起 编译 , 若 接口 和 实现 不对应, 会 报错 。
这 本身也是 和 Native Code 交互 的 规范 和 技术 。
可以 来 看 一段 D++ 代码 :
等待接收 Windows 消息 也可以 算作一次 IO , 如此, 刚刚 的 编程模式 也可以用于 窗体程序 的 Windows 消息循环 。
事实上, 用 刚刚 的 编程模式, Windows 消息循环 和 多个 IO 可以 并发/并行 的 进行,(对 Pool 来说 是 并发,对这些 IO 自身来说 是 并行 )
可以 为 每一个 IO(Windows 消息)执行 回调,也可以 等待 一组 IO(Windows 消息) 都 完成(收到) 时 执行 回调, 后者 类似 Promise ,也可以说 就是 Promise 。
前者 和 后者 可以混合, 既 可以 为 一些 IO 单独 执行 回调,也可以 把 单独执行回调 的 IO 或 其它 的 IO 合成一组,等这些 IO 全部完成时 执行 回调 。
Pool 和 Bag 一开始的时候可以从 Grid Heap 申请一个 小 的 Grid , 比如 8 KB ,
new 子对象从 Grid 中 顺序 分配空间 ,
当 new 的 子对象 的 Size 大于 Grid 的 剩余空间 时,
如果 子对象 Size 大于 Grid 空间 的 1/10 , 则 从 Grid Heap 申请一个 更大一级 的 Grid ,比如 128 KB,
如果 子对象 Size 小于 Grid 空间 的 1/10 , 则 从 Grid Heap 申请 一个 同级 的 Grid, 8 KB 。
如果 Bag 当前 所有 的 Grid 的 空间总和(Grid 的 固有空间总和,不是 已使用空间总和 也不是 剩余空间 总和)达到了 当前 Grid 中 最高级(最大) 的 Grid 的 更高一级 Grid 的 Size,那么接下来 再申请 Grid, 则 申请 这个 更高一级 的 Grid 。
比如, 当前 Bag 的 Grid 中有 8 KB 的, 有 128 KB 的,最高级(最大)的 是 128 KB , 且 这些 Grid 加起来 已经 达到了 1 MB 以上,则 下次 申请 Grid 就 申请 1MB 的 Grid 。
池对象 的 Size 只要 小于 最高级别 (最大)的 Grid 的 1/10 就行了 。
其实 最大 的 Grid 也可以 比 1 MB 大,比如 10 MB , 100 MB ,
根据 上面的规则, 若 当前 Bag 内 所有 Grid 空间 总和 是 101 MB, 则再申请 Grid 就是 100 MB, 于是 Bag 内 Grid 总空间 达到 201 MB 。
程序员 可以 通过 Pool / Bag 的 构造函数 参数 指定 默认 Grid Size(级别),这样 Bag 一开始就按照 指定 的 Grid Size 申请 Grid 。
比如 有 的 Bag 要存的数据比较多, 就可以指定 Grid Size 为 128 KB 或 1MB,
这样可以减少 初始 的 一段时间内 从 Grid Heap 申请 Grid 的 次数 。
上文说了, Heap 是 多核共享, 因此 管理 Heap 的 数据 是 原子类型, 每次 分配 和 回收 空间 要 CAS Lock 和 修改 Heap 管理数据, CAS Lock 也是 原子操作, 也就是说, 每次 从 Heap 里 分配 和 回收 空间 需要 若干次 原子操作, 从 Grid Heap 里 分配 和 回收空间 也许需要 100 个 时钟周期 , 时间花费 是 比较多的 ,是 比较重型 的 操作 。
减少 从 堆(Heap)里 申请空间(申请 Grid )的 次数 可以 提高性能 。
堆对象 可以返回 堆对象, 当 堆对象 返回到 D++ 层面时, 按 引用计数 管理回收,
这就要求, D++ 层面 的 堆对象, 应是 树形结构, 简单的说,就是 没有 循环引用 。
先说这些, 具体 的 内容 还有一些, 暂时 懒得写了 。