0
点赞
收藏
分享

微信扫一扫

d原位数组扩展优化


​​原文​​​ 我注意到D运行时的​​原位数组扩展优化​​仅适合特定内存对齐的数组数据.
除了重复向​​数组​​加元素之外,使用以下程序来测试,
​-version=neither​​不会删除元素(这也是"好")
​-version=bad​​丢弃前面元素(​​移动​​窗口为1)
​-'version=good'​​仅当​​元素数据​​达到​​某个对齐值​​时,才会删除元素.这是期望的吗?

import std.stdio;
import std.range;

// 请取消一个注释.
// version = bad; // 无原位扩展
// version = good; // 有原位扩展
// version = neither;// 有原位扩展

mixin assertSingleVersionOf!("bad", "good", "neither");

void main() {
struct S {
ubyte[4] data; // 不同的合理尺寸
// 如5,是不行的.
}

S[] arr;

foreach (i; 0 .. 100_000) {
const oldCap = arr.capacity;
const oldPtr = arr.ptr;

arr ~= S.init;

if (arr.capacity != oldCap) {
// 需要扩展数组.
if (arr.ptr == oldPtr) {
// ... 但是指针不变
writefln!"原位扩展元素: %,s 容量: %,s -> %,s 指针: %s"(
i, oldCap, arr.capacity, arr.ptr);
}
}

version (neither) {
// 不删,仅扩展
}

version (bad) {
// 删1元素,其余禁止
arr = arr[1..$];

// 这样也没用
arr.assumeSafeAppend();
}

version (good) {
// 删除等于 2048 字节的前端元素有效
// 可能是GC相关数.

enum magic = 2048;
enum elementsPerPage = magic / S.sizeof;

if (arr.length == elementsPerPage) {
arr = arr[elementsPerPage..$];
}
}
}
}

// 有用模板
mixin template assertSingleVersionOf(args...) {
import std.format :;

static assert (1 >= {
size_t count = 0;
static foreach (arg; args) {
static assert (is (typeof(arg) == string));
mixin (format!q{
version (%s) {
++count;
}
}(arg));
}
return count;
}(), format!"取<=1的%(%s, %)"([args]));
}

不,它基于两个因素:它是比​​页​​​大小更大的块?后面有​​释放页​​​吗?
​​​较小​​​的块存储在​​页面​​​大小池中,且不能​​组合​​​在一起.如,你不能合并2个​​16​​​字节的块起来形成​​32​​​字节的块.
​​​bad​​​版本失败原因:必须​​重新分配​​​时,它仍然只分配​​1个​​​元素.
现在一个页面一般是​​​4096​​​字节.为什么​​2048​​​数有效呢?因为为了存储一个​​2048​​​字节的数组,你需要​​2048​​​字节和(如用于​​析构​​​和追加容量的​​typeinfo​​​的)​​元数据​​​.这需要一整页.
请注意,一旦达到​​​页面大小​​​,即使只是附加到​​尾切片​​​,就会​​优化​​​.如,当​​容量​​​小于元素的"​​神奇​​​"数量时,​​保存​​​该数量元素.然后"每个循环​​删除​​一个元素"应该使用优化.

如​​16​​​字节的相同块仍有​​空间​​​.为什么不使用​​剩余​​​部分?
这是更简单的测试,​​​结果​​​好于期望.​​c数组​​​是我想要做的.在该测试中可工作,甚至不必调用​​assumeSafeAppend()​​​(这必须是你所说的"​​尾切片​​").

import std.stdio;

void main() {
ubyte[] a;
a ~= 0;
a.length = 0;
a.assumeSafeAppend(); // 需要
assert(a.capacity == 15);

// 同上
ubyte[] b;
b ~= 0;
b = b[0..0];
b.assumeSafeAppend(); // 需要
assert(b.capacity == 15);

ubyte[] c;
c ~= 0;
c = c[1..$];
// c.assumeSafeAppend();
assert(c.capacity == 14);
}

它总共有​​16​​​个字节(​​64​​​位):​​void*+size_t​​​,我打印​​.ptr​​​时我看到了该:变化总是​​0x10​​.

void increaseCapacityWithoutAllocating(T)(ref T[] arr) {
// ...
}//不分配增加容量.

可以在​​运行时​​​调用类似在​​object.d​​​中调用​​_d_arrayshrinkfit()​​​的​​assumeSafeAppend()​​方法吗?

不,是​​现有块​​​长度.
​​​内存分配器​​​中的​​所有内容​​​都以​​页​​​为单位.​​池​​​是一堆页.大块是多页,小块是​​分成​​​相同大小块的页.
想分配块时,如果它小于半页,则它会进入​​​小池​​​,你不能粘贴​​2个块​​​在一起.
如果它大于​​​半​​​页,则它需要​​多页​​​大小块.他们太大了,不能不管,所以​​分配器​​​只是抓住一些有​​足够空闲​​​页面池,然后​​返回它​​​.这些块可​​按需​​​缝合在一起.一旦​​释放​​​,也可把它们​​分成​​​页面.在此,​​容量​​可不复制就增长.

它会,直到它不能.然后需要​​重新分配​​​.
请注意,如果删除​​​头元素​​​,则指向数组的​​切片​​​.
​​​A​​​表示被​​当前切片​​​使用,​​x​​​是​​已分配​​​,但未被​​数组引用​​​.​​.​​​是块的​​一部分​​​但未使用(但可供使用)“.​​M​​是"元数据”.

auto arr = new ubyte[10];      
// AAAA AAAA AA.. ...M
arr = arr[1 .. $];
// xAAA AAAA AA.. ...M
arr ~= 0;
// xAAA AAAA AAA. ...M
arr ~= 0;
// xAAA AAAA AAAA ...M
arr = arr[3 .. $];
// xxxx AAAA AAAA ...M
arr ~= cast(ubyte[])[1, 2, 3];
// xxxx AAAA AAAA AAAM // 满了!
arr ~= 1;
// AAAA AAAA AAAA ...M //分配后,改了.

请注意,在​​最后实例​​​中,它现在已​​移动​​​到不同的​​16​​​字节块中,​​原始块​​​仍然完好无损,只是未指向它.最终被垃集.
但是,可看到它只​​​分配​​​了可容纳​​切片​​​已包含内容+新元素的空间.
是的,​​​数组切片​​​的"​​可附加性​​​"取决于它是否在"已用"空间的​​末尾​​​.否则,如果更早​​结束​​​,附加会占用可能在​​其他地方引用​​​的内存.如果​​稍后​​​结束,则你在​​代码​​中做错了,现在已被破坏了.

并不总是​​存储​​​元数据.另外,它针对​​块大小​​​优化了.如,​​256​​​字节或更小的块只需要​​一个字节​​​来存储"​​已用​​​"空间.
仅在需要时存储​​​析构​​​数组元素所需的​​TypeInfo​​​(如,​​int​​数组,就不需要).


举报

相关推荐

0 条评论