Node.js 为什么nodejs阵列移位/推送循环比阵列长度87369慢1000倍?
为什么nodejs阵列移位/推送操作的速度在阵列大小中不是线性的?在87370处有一个戏剧性的膝盖,完全粉碎了这个系统 试试这个,先用q中的87369个元素,然后用87370。(或者,在64位系统上,试试85983和85984。)对我来说,前者运行时间为0.05秒;后者,在80秒内——慢1600倍。(在节点为v0.10.29的32位debian linux上观察到)Node.js 为什么nodejs阵列移位/推送循环比阵列长度87369慢1000倍?,node.js,performance,v8,Node.js,Performance,V8,为什么nodejs阵列移位/推送操作的速度在阵列大小中不是线性的?在87370处有一个戏剧性的膝盖,完全粉碎了这个系统 试试这个,先用q中的87369个元素,然后用87370。(或者,在64位系统上,试试85983和85984。)对我来说,前者运行时间为0.05秒;后者,在80秒内——慢1600倍。(在节点为v0.10.29的32位debian linux上观察到) q=[]; //用一些数据预加载队列 对于(i=0;i我开始深入研究v8源代码,但我仍然不理解它 我检测了deps/v8/src/
q=[];
//用一些数据预加载队列
对于(i=0;i我开始深入研究v8源代码,但我仍然不理解它
我检测了deps/v8/src/builtins.cc:MoveElemens(从Builtin_ArrayShift调用,它使用memmove实现换档),它清楚地显示了减速:每秒只有1000次换档,因为每次换档需要1ms:
AR: at 1417982255.050970: MoveElements sec = 0.000809
AR: at 1417982255.052314: MoveElements sec = 0.001341
AR: at 1417982255.053542: MoveElements sec = 0.001224
AR: at 1417982255.054360: MoveElements sec = 0.000815
AR: at 1417982255.055684: MoveElements sec = 0.001321
AR: at 1417982255.056501: MoveElements sec = 0.000814
其中memmove是0.000040秒,bulk是heap->RecordWrites(deps/v8/src/heap inl.h):
void Heap::RecordWrites(地址、int start、int len){
如果(!InNewSpace(地址)){
对于(int i=0;i
即(存储缓冲区inl.h)
void StoreBuffer::Mark(地址addr){
断言(!heap_u->cell_space()->Contains(addr));
断言(!heap_uu->code_uspace()->Contains(addr));
地址*top=reinterpret\u cast(heap\uu->store\u buffer\u top());
*top++=addr;
堆->公共设置存储缓冲区顶部(顶部);
if((重新解释强制转换(顶部)&kStoreBufferOverflowBit)!=0){
断言(top==极限值);
紧凑型();
}否则{
断言(顶部<极限值);
}
}
当代码运行缓慢时,会运行shift/push操作,然后对每个MoveElements运行5-6次调用Compact()
。当代码运行快速时,MoveElements直到最后几次才被调用,当它完成时只调用一次压缩
我猜内存压缩可能会带来冲击,但对我来说还没有到位
编辑:忘记上次关于输出缓冲工件的编辑,我是在过滤重复项。Shift对于数组来说是一个非常慢的操作,因为您需要移动所有元素,但是V8能够使用一个技巧在数组内容适合页面(1mb)时快速执行它
空数组从4个插槽开始,当您继续按下时,它将使用公式1.5*(旧长度+1)+16调整数组大小
var j = 4;
while (j < 87369) {
j = (j + 1) + Math.floor(j / 2) + 16
console.log(j);
}
所以你的数组大小实际上是124517个条目,这使得它太大了
实际上,您可以将阵列预分配到正确的大小,并且它应该能够再次快速移动:
var q = new Array(87369); // Fits in a page so fast shift is possible
// preload the queue with some data
for (i=0; i<87369; i++) q[i] = {};
var q=new Array(87369);//适合一个页面,因此可以快速移动
//用一些数据预加载队列
对于(i=0;i这个bug已经报告给了谷歌,谷歌没有研究这个问题就关闭了它
从队列(数组)移出并调用任务(函数)时
GC(?)暂停的时间过长
114467班还可以
114468班次有问题,出现症状
答复如下:
他与此无关,也没有任何拖延
shift()是一个昂贵的操作,因为它需要所有的数组
要移动的元素。对于堆的大多数区域,V8实现了
隐藏此成本的特殊技巧:它只是将指针指向
由一个物体开始,有效地切断了第一个物体
但是,当数组太大,必须将其放入
“大对象空间”,此技巧不能在对象启动时应用
必须对齐,因此在每个.shift()操作中,所有元素都必须对齐
实际上是在记忆中移动
我不确定我们能做多少,如果你想
JavaScript中的“Queue”对象,对于
.enqueue()和.dequeue()操作,您可能希望实现
拥有
编辑:我刚刚捕捉到了微妙的“所有元素都必须移动”部分——RecordWrites不是GC而是实际的元素副本吗?数组内容的memmove是0.04毫秒。RecordWrites循环是1.1毫秒运行时间的96%
编辑:如果“对齐”意味着第一个对象必须位于第一个地址,memmove就是这么做的。什么是记录写入?我在85983(5,不是6)中修复了输入错误,谢谢,但问题在于shift
,而不是unshift
——数组越来越小(它已通过附件预先分配到87369)。v8正在使用memmove()移动它们只需0.04毫秒;总时间为1.10毫秒,慢25倍。减速来自内部记录写入。在这种情况下为什么需要记录写入?它有什么作用?我尝试了你的建议,将其更改为q=new Array(20000)
,但速度仍然很慢。您无法使用预分配。按
,它将动态调整大小,当数字变大时,调整大小非常方便,因为调整大小操作非常昂贵(需要将所有元素复制到新数组)但我不是计时推送,我是计时移位。我的观点是,到时间移位运行时,阵列将已分配。使用前的分配是预分配。当移位运行时,阵列已达到最大大小;它不会变得更大。由于动态调整大小,开始移位时的阵列大小为124517,因此阵列将是对于新空间来说太大,无法实现快速移动。我建议预先分配准确的大小,因为87000个项目适合新空间。在v8使用的分代垃圾收集中,记录写入在某种意义上是一个钩子,用于检查是否正在使旧空间中的对象指向新空间中的对象。啊,这很有用,谢谢。mem也是如此move()移动数组中的指针或对象本身?没有创建新对象,也没有对列表进行gc
void StoreBuffer::Mark(Address addr) {
ASSERT(!heap_->cell_space()->Contains(addr));
ASSERT(!heap_->code_space()->Contains(addr));
Address* top = reinterpret_cast<Address*>(heap_->store_buffer_top());
*top++ = addr;
heap_->public_set_store_buffer_top(top);
if ((reinterpret_cast<uintptr_t>(top) & kStoreBufferOverflowBit) != 0) {
ASSERT(top == limit_);
Compact();
} else {
ASSERT(top < limit_);
}
}
var j = 4;
while (j < 87369) {
j = (j + 1) + Math.floor(j / 2) + 16
console.log(j);
}
23
51
93
156
251
393
606
926
1406
2126
3206
4826
7256
10901
16368
24569
36870
55322
83000
124517
var q = new Array(87369); // Fits in a page so fast shift is possible
// preload the queue with some data
for (i=0; i<87369; i++) q[i] = {};