我试图用谷歌的WebAssembly引擎(使用当前版本的Google (版本83.0.4103.106,64位)和在C++程序中嵌入V8 (8.5.183版本)来执行一个相当琐碎的V8基准。所有基准测试都在macOS 10.14.6上使用英特尔i7 8850 H处理器执行。没有使用RAM交换。
我使用下面的C代码作为基准测试。(请注意,运行时在当前的Intel i7上是按秒顺序排列的)
static void init(int n, int path[1000][1000]) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
path[i][j] = i*j%7+1;
if ((i+j)%13 == 0 || (i+j)%7==0 || (i+j)%11 == 0) {
path[i][j] = 999;
}
}
}
}
static void kernel(int n, int path[1000][1000]) {
for (int k = 0; k < n; k++) {
for(int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
path[i][j] = path[i][j] < path[i][k] + path[k][j] ? path[i][j] : path[i][k] + path[k][j];
}
}
}
}
int path[1000][1000];
int main(void) {
int n = 1000;
init(n, path);
kernel(n, path);
return 0;
}这可以通过https://wasdk.github.io/WasmFiddle/轻松地执行。以最基本的方式测量时间的对应JS代码如下:
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, wasmImports);
var a = new Date();
wasmInstance.exports.main();
var b = new Date();
log(b-a);我在Google浏览器(例如WasmFiddle或自定义网站)中获得的结果是(连续执行多次),以毫秒为单位:
3687
1757
1837
1753
1726
1731
1774
1741
1771
1727
3549
1742
1731
1847
1734
1745
3515
1731
1772请注意,异常值的执行速度是其他值的一半。如何以及为什么仍然有如此一致的表现的离群点?尽可能多地注意确保没有其他进程占用CPU时间。
对于嵌入式版本,已经使用以下构建配置从源代码构建了单块V8库:
is_component_build = false
is_debug = false
target_cpu = "x64"
use_custom_libcxx = false
v8_monolithic = true
v8_use_external_startup_data = false
v8_enable_pointer_compression = false嵌入V8库并执行V8脚本的V8代码( Wasm代码是WasmFiddle编译器生成的确切代码):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "include/libplatform/libplatform.h"
#include "include/v8.h"
int main(int argc, char* argv[]) {
// Initialize V8.
v8::V8::InitializeICUDefaultLocation(argv[0]);
v8::V8::InitializeExternalStartupData(argv[0]);
std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
v8::V8::InitializePlatform(platform.get());
v8::V8::Initialize();
// Create a new Isolate and make it the current one.
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
{
v8::Isolate::Scope isolate_scope(isolate);
// Create a stack-allocated handle scope.
v8::HandleScope handle_scope(isolate);
// Create a new context.
v8::Local<v8::Context> context = v8::Context::New(isolate);
v8::Context::Scope context_scope(context);
{
const char csource[] = R"(
let bytes = new Uint8Array([
0x0, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x01, 0x85, 0x80, 0x80, 0x80, 0x00, 0x01, 0x60,
0x00, 0x01, 0x7F, 0x03, 0x82, 0x80, 0x80, 0x80, 0x00, 0x01, 0x00, 0x04, 0x84, 0x80, 0x80, 0x80,
0x00, 0x01, 0x70, 0x00, 0x00, 0x05, 0x83, 0x80, 0x80, 0x80, 0x00, 0x01, 0x00, 0x3E, 0x06, 0x81,
0x80, 0x80, 0x80, 0x00, 0x00, 0x07, 0x91, 0x80, 0x80, 0x80, 0x00, 0x02, 0x06, 0x6D, 0x65, 0x6D,
0x6F, 0x72, 0x79, 0x02, 0x00, 0x04, 0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x0A, 0x8F, 0x82, 0x80,
0x80, 0x00, 0x01, 0x89, 0x82, 0x80, 0x80, 0x00, 0x01, 0x08, 0x7F, 0x41, 0x00, 0x21, 0x02, 0x41,
0x10, 0x21, 0x05, 0x03, 0x40, 0x20, 0x05, 0x21, 0x07, 0x41, 0x00, 0x21, 0x04, 0x41, 0x00, 0x21,
0x03, 0x03, 0x40, 0x20, 0x07, 0x20, 0x04, 0x41, 0x07, 0x6F, 0x41, 0x01, 0x6A, 0x41, 0xE7, 0x07,
0x20, 0x02, 0x20, 0x03, 0x6A, 0x22, 0x00, 0x41, 0x07, 0x6F, 0x1B, 0x41, 0xE7, 0x07, 0x20, 0x00,
0x41, 0x0D, 0x6F, 0x1B, 0x41, 0xE7, 0x07, 0x20, 0x00, 0x41, 0x0B, 0x6F, 0x1B, 0x36, 0x02, 0x00,
0x20, 0x07, 0x41, 0x04, 0x6A, 0x21, 0x07, 0x20, 0x04, 0x20, 0x02, 0x6A, 0x21, 0x04, 0x20, 0x03,
0x41, 0x01, 0x6A, 0x22, 0x03, 0x41, 0xE8, 0x07, 0x47, 0x0D, 0x00, 0x0B, 0x20, 0x05, 0x41, 0xA0,
0x1F, 0x6A, 0x21, 0x05, 0x20, 0x02, 0x41, 0x01, 0x6A, 0x22, 0x02, 0x41, 0xE8, 0x07, 0x47, 0x0D,
0x00, 0x0B, 0x41, 0x00, 0x21, 0x06, 0x41, 0x10, 0x21, 0x05, 0x03, 0x40, 0x41, 0x10, 0x21, 0x00,
0x41, 0x00, 0x21, 0x01, 0x03, 0x40, 0x20, 0x01, 0x41, 0xA0, 0x1F, 0x6C, 0x20, 0x06, 0x41, 0x02,
0x74, 0x6A, 0x41, 0x10, 0x6A, 0x21, 0x02, 0x41, 0x00, 0x21, 0x07, 0x03, 0x40, 0x20, 0x00, 0x20,
0x07, 0x6A, 0x22, 0x04, 0x20, 0x04, 0x28, 0x02, 0x00, 0x22, 0x04, 0x20, 0x05, 0x20, 0x07, 0x6A,
0x28, 0x02, 0x00, 0x20, 0x02, 0x28, 0x02, 0x00, 0x6A, 0x22, 0x03, 0x20, 0x04, 0x20, 0x03, 0x48,
0x1B, 0x36, 0x02, 0x00, 0x20, 0x07, 0x41, 0x04, 0x6A, 0x22, 0x07, 0x41, 0xA0, 0x1F, 0x47, 0x0D,
0x00, 0x0B, 0x20, 0x00, 0x41, 0xA0, 0x1F, 0x6A, 0x21, 0x00, 0x20, 0x01, 0x41, 0x01, 0x6A, 0x22,
0x01, 0x41, 0xE8, 0x07, 0x47, 0x0D, 0x00, 0x0B, 0x20, 0x05, 0x41, 0xA0, 0x1F, 0x6A, 0x21, 0x05,
0x20, 0x06, 0x41, 0x01, 0x6A, 0x22, 0x06, 0x41, 0xE8, 0x07, 0x47, 0x0D, 0x00, 0x0B, 0x41, 0x00,
0x0B
]);
let module = new WebAssembly.Module(bytes);
let instance = new WebAssembly.Instance(module);
instance.exports.main();
)";
// Create a string containing the JavaScript source code.
v8::Local<v8::String> source = v8::String::NewFromUtf8Literal(isolate, csource);
// Compile the source code.
v8::Local<v8::Script> script = v8::Script::Compile(context, source).ToLocalChecked();
// Run the script to get the result.
v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();
}
}
// Dispose the isolate and tear down V8.
isolate->Dispose();
v8::V8::Dispose();
v8::V8::ShutdownPlatform();
delete create_params.array_buffer_allocator;
return 0;
}我将其汇编如下:
g++ -I. -O2 -Iinclude samples/wasm.cc -o wasm -lv8_monolith -Lout.gn/x64.release.sample/obj/ -pthread -std=c++17在使用time ./wasm执行时,我的执行时间在4.9到5.1之间--几乎是Chrome/WasmFiddle执行时间的三倍!我错过什么了吗?也许是一些优化开关?这个结果是完全可重复的,我甚至测试了不同版本的V8库--仍然是相同的结果。
发布于 2020-06-18 23:21:30
啊,微基准的乐趣:-)
V8有两个用于Wasm的编译器:一个非优化的基线编译器,它生成代码的速度非常快,另一个优化编译器需要花费相当长的时间来生成代码,但该代码的速度通常是原来的两倍。加载模块时,当前版本首先使用基线编译器编译所有函数。一旦完成,执行就可以启动,优化的编译作业将在后台运行。当优化的编译作业完成后,将交换相应函数的代码,下一次函数调用将使用它。(这里的细节很可能在未来发生变化,但总的原则将保持不变。)这样,典型的应用程序既具有良好的启动延迟,又具有良好的峰值性能。
但是,就像任何启发式或策略一样,你可以设计出一个错误的案例.
在基准测试中,每个函数只调用一次。在快速情况下,优化kernel将在init返回之前完成。在缓慢的情况下,kernel在其优化编译工作完成之前被调用,因此它的基线版本运行。显然,在直接嵌入V8时,可以可靠地获得后一种场景,而在Chrome中通过WasmFiddle运行时,大多数情况下都是前者,但并不总是如此。
我无法解释为什么您的自定义嵌入运行比Chrome中的慢速情况还要慢;在我的机器上(在Chrome中,OTOH,我看到的是一个更大的增量:大约1100 my用于快速运行,4400 my用于慢跑);然而,我使用的是d8外壳,而不是自己的嵌入。有一点不同的是,当在命令行上使用time进行度量时,您将包括进程启动和初始化,而Date.now()调用main()时并不包括进程启动和初始化。但这应该只占10-50毫秒左右的时间,而不是3.6秒的→5.0秒差。
虽然对于您的微基准测试来说,这种情况可能很不幸,但它通常是按预期工作的,即不是bug,因此不太可能在V8方面发生变化。要使基准测试更好地反映真实世界的行为,您可以做几件事(假设这个基准并不完全代表一些实际的应用程序):
var wasmModule =新WebAssembly.Module(wasmCode);var wasmInstance =新WebAssembly.Instance(wasmModule,wasmImports);window.setTimeout(() => { var a= Date.now();wasmInstance.exports.main();var b= Date.now();log(b-a);},10);
在我对d8的测试中,我发现即使是一个愚蠢的忙碌-等待也起了作用:
让wait = Date.now() + 10;虽然(Date.now() <等待) {}使基准测试更大、更复杂:拥有和执行更多不同的函数,而不只是把99%的时间花在一行上。
(FWIW是最早支持V8的WebAssembly版本,它没有分层,只有优化编译。所以模块总是要等待它的完成。这不是一个好的用户体验;对于大型模块来说,等待时间可能是几十秒。拥有一个基线编译器显然是更好的整体解决方案,即使它的代价是不能立即获得最大的性能。在实际操作中,在人工一行程序上看起来很好并不重要;为大型实际应用程序提供良好的用户体验非常重要。)
https://stackoverflow.com/questions/62460190
复制相似问题