我正在用NodeJS编写合并排序的JavaScript实现,因为本机V8 sort
函数使用快速排序,在最坏的情况下它不如合并排序有效。为了测试性能,我制作了一个由10000个元素组成的数组,这些元素已预先排序(最坏情况是快速排序),并确定了使用这两个函数对该列表进行排序所花费的时间。在一次执行中,本机sort
函数大约需要16毫秒,而我的实现大约需要9毫秒。但是,当同时执行两次100次时,本机sort
平均大约需要2.1毫秒,而我的const { performance } = require('perf_hooks');
function merge(a, b, func) {
const array = new Array(a.length + b.length);
let aIndex = 0;
let bIndex = 0;
while(aIndex + bIndex < array.length) {
if(aIndex >= a.length || func(a[aIndex], b[bIndex]) > 0) {
array[aIndex + bIndex] = b[bIndex++];
} else {
array[aIndex + bIndex] = a[aIndex++];
}
}
return array;
}
function mergeSort(list, func) {
if(list.length <= 1) {
return list;
}
const half = list.length / 2;
return merge(mergeSort(list.slice(0, half), func), mergeSort(list.slice(half), func), func);
}
function time(func, iters) {
let sum = 0;
for(let i = 0; i < iters; i++) {
let startTime = performance.now();
func();
sum += performance.now() - startTime;
}
return sum / iters;
}
const arr = [...Array(10000).keys()];
const sortFunc = (a, b) => a - b;
console.log("JavaScript built-in sort execution time, one iteration:")
console.log(time(() => arr.sort(sortFunc), 1)); // ~16
console.log("Manually implemented merge sort execution time, one iteration:")
console.log(time(() => mergeSort(arr, sortFunc), 1)); // ~9
console.log();
console.log("JavaScript built-in sort average execution time, 100 iterations:")
console.log(time(() => arr.sort(sortFunc), 100)); // ~2.1
console.log("Manually implemented merge sort average execution time, 100 iterations:")
console.log(time(() => mergeSort(arr, sortFunc), 100)); // ~4.3
大约需要4.3毫秒。这是我的代码:
sort
为什么重复执行一次却比一次执行快得多?为什么这种改进对于本机slice
函数更明显?
编辑:通过跟踪数组索引而不是使用sort
方法,我能够使算法更有效。现在,在预排序数组上使用我的代码时,它始终击败v8的本机const { performance } = require('perf_hooks');
function merge(a, b, func) {
const array = new Array(a.length + b.length);
let aIndex = 0;
let bIndex = 0;
while(aIndex + bIndex < array.length) {
if(aIndex >= a.length || func(a[aIndex], b[bIndex]) > 0) {
array[aIndex + bIndex] = b[bIndex++];
} else {
array[aIndex + bIndex] = a[aIndex++];
}
}
return array;
}
function mergeSortRec(list, func, start, limit) {
if (limit === 1) {
return [list[start]];
}
const half = limit / 2 | 0;
return merge(mergeSortRec(list, func, start, half), mergeSortRec(list, func, half + start, limit - half), func);
}
function mergeSort(list, func) {
return mergeSortRec(list, func, 0, list.length);
}
function time(func) {
let startTime = performance.now();
func();
return performance.now() - startTime;
}
const sortFunc = (a, b) => a - b;
console.log();
console.log("--- Sequential array ---");
console.log();
const sequenceArr = [...Array(10000).keys()];
console.log("JavaScript built-in sort execution time, one iteration:");
console.log(time(() => sequenceArr.slice(0).sort(sortFunc)));
console.log("Manually implemented merge sort execution time, one iteration:");
console.log(time(() => mergeSort(sequenceArr, sortFunc)));
let sum = 0;
for(let i = 0; i < 100; i++) {
const array = sequenceArr.slice(0);
sum += time(() => array.sort(sortFunc));
}
console.log("JavaScript built-in sort average execution time, 100 iterations:");
console.log(sum / 100);
sum = 0;
for(let i = 0; i < 100; i++) {
sum += time(() => mergeSort(sequenceArr, sortFunc))
}
console.log("Manually implemented merge sort average execution time, 100 iterations:");
console.log(sum / 100);
console.log();
console.log("--- Randomized array ---");
console.log();
const randomArrays = new Array(101);
for(let i = 0; i < 101; i++) {
randomArrays[i] = new Array(10000);
for(let j = 0; j < 10000; j++) {
randomArrays[i][j] = Math.random() * 5000 | 0;
}
}
console.log("JavaScript built-in sort execution time, one iteration:");
console.log(time(() => randomArrays[100].slice(0).sort(sortFunc)));
console.log("Manually implemented merge sort execution time, one iteration:");
console.log(time(() => mergeSort(randomArrays[100], sortFunc)));
sum = 0;
for(let i = 0; i < 100; i++) {
const array = randomArrays[i].slice(0)
sum += time(() => array.sort(sortFunc));
}
console.log("JavaScript built-in sort average execution time, 100 iterations:");
console.log(sum / 100);
sum = 0;
for(let i = 0; i < 100; i++) {
sum += time(() => mergeSort(randomArrays[i], sortFunc))
}
console.log("Manually implemented merge sort average execution time, 100 iterations:");
console.log(sum / 100);
,但在随机数组上却不出所料。这是那些有兴趣的代码:
dataframe