在D语言中能否在运行时创建内存连续的多维数组?
D语言完全可以实现你的需求。你尝试的写法无法编译是因为int[nr][nc]属于静态数组类型,其维度必须是编译期常量,不能使用运行时变量指定。以下是两种成熟的实现方案:
方案1:自定义Array2D结构体(通用最优方案)
通过简单的结构体包装一维数组,实现[r][c]语法,无任何额外内存开销,性能等价于手动计算偏移的一维数组:
import core.memory; struct Array2D(T) { private T* data; size_t rows; // 行数nr size_t cols; // 列数nc /// 传入行数、列数创建数组 this(size_t nr, size_t nc) { rows = nr; cols = nc; data = (new T[nr * nc]).ptr; } /// 实现 arr[r][c] 索引 T[] opIndex(size_t r) @nogc nothrow @trusted { // 开启边界检查可取消下一行注释 // assert(r < rows, "行索引越界"); return data[r * cols .. (r + 1) * cols]; } /// 可选:支持 arr[r,c] 直接索引,省略中间切片生成步骤,性能更高 ref T opIndex(size_t r, size_t c) @nogc nothrow @trusted { // 开启边界检查可取消下一行注释 // assert(r < rows && c < cols, "索引越界"); return data[r * cols + c]; } /// 可选:暴露原始指针,方便对接C接口或手动操作内存 T* ptr() @property @nogc nothrow { return data; } }
使用示例:
void main() { ulong nr = 3, nc = 4; auto arr = Array2D!int(nr, nc); arr[1][2] = 42; assert(arr.ptr[1 * nc + 2] == 42); }
方案2:chunks简便实现(快速开发用)
如果不想自己写结构体,可以直接用标准库std.range.chunks将一维数组按行拆分,无需额外内存开销:
import std.range; void main() { ulong nr = 3, nc = 4; auto data = new int[nr * nc]; auto arr = data.chunks(nc); arr[1][2] = 42; assert(data[1 * nc + 2] == 42); }
如果需要完全兼容D的动态数组接口,可以在chunks后加.array,但会额外存储每行的指针,产生少量内存开销。
方案性能对比
以下是对10,000,000 × 20的double类型数组的实测结果,测试函数如下:
import std.math; auto busywork(T)(T p, size_t nr, size_t nc) { foreach (r; 0..nr) { foreach (c; 0..nc) { p[r][c] = r + log(1.0 + c); } } double result = 1.0; foreach (t; 0..1) { foreach (r; 0..nr) { foreach (c; 0..nc) { result += log(1.0 + pow(p[r][c], 3)); } } } return result; }
对比的5种实现:
- baseline:一维数组,手动使用
p[r * nc + c]索引 - baseline_dynamic:D原生二维动态数组
- chunks_array:chunks方案加
.array调用 - chunks:chunks方案不加
.array - array2d:上述自定义Array2D方案,关闭opIndex边界检查
运行耗时
下表为5次运行的中位数耗时,单位为毫秒:
| 方案 | dmd -O | gdc -O3 | gdc -Os | ldc -O3 |
|---|---|---|---|---|
| baseline | 13592 +/- 1758 | 20978 +/- 434 | 15452 +/- 1178 | 20778 +/- 1017 |
| baseline_dynamic | 13481 +/- 339 | 20782 +/- 501 | 13476 +/- 209 | 20632 +/- 156 |
| chunks | 15800 +/- 144 | 21014 +/- 176 | 13676 +/- 118 | 21203 +/- 212 |
| chunksarray | 12903 +/- 649 | 19648 +/- 1080 | 12895 +/- 519 | 19770 +/- 816 |
| array2d | 12606 +/- 606 | 19844 +/- 1042 | 12640 +/- 540 | 19724 +/- 953 |
内存开销
- chunks和array2d相对于baseline无额外内存开销
- 所有编译器下chunksarray均有9%的内存开销
- baseline_dynamic在dmd下有18%的开销,在gcc和ldc下开销达41%
相对开销会随数组维度和元素类型变化
结论
-O3和-Os参数的巨大性能差异,以及dmd -O编译下array2d部分运行结果性能优于baseline的现象(对汇编的检查显示dmd没有内联opIndex)表明,当数组占用系统内存比例较高时,缓存失效次数是影响性能的主导因素。设置-boundscheck=off后dmd -O编译的baseline性能与array2d持平,这就是array2d有时表现更好的原因。
chunksarray的优势是代码更短,语法和动态数组一致,当nc和/或T.sizeof足够大,相对内存开销影响较小时可以选择该方案,通用场景下array2d是最优选择。
内容的提问来源于stack exchange,提问作者Ray
相关产品推荐
相关产品推荐

