c++ - 使用 webassembly 在浏览器中查询大型数据集
问题描述
为了论证,假设浏览器允许在 WebAssembly 应用程序中使用4GB 内存。忽略压缩和其他数据存储考虑,如果用户有一个 3GB 的本地 csv 文件,我们可以使用 webassembly(当然也可以是 javascript)在内存中查询该数据。例如,如果用户的数据具有以下格式:
ID | 国家 | 数量 |
---|---|---|
1 | 我们 | 12 |
2 | 国标 | 11 |
3 | 德 | 7 |
然后在几行代码中,我们可以做一个基本算法来过滤到ID=2
,即 SQL 等价物SELECT * FROM table WHERE id=2
。
现在,我的问题是是否有可能在任何浏览器中(并且可能选择实验标志和/或某些用户偏好),以便可以对不适合内存的文件进行查询,即使正确压缩也是如此。例如,在这篇博文中,加载并查询了一个 ~500GB 的文件。我知道 500GB 的数据并未完全加载到内存中,并且可能存在面向列的数据结构,因此只需要读取某些列,但无论哪种方式,操作系统都可以访问文件系统,因此文件比可以使用可用内存。
这是否可以在 webassembly 浏览器应用程序中以任何方式进行?如果是这样,它可以如何完成的大纲是什么?我知道这个问题可能需要一些研究,所以当它可用于赏金时,我可以添加 500 点赏金来鼓励答案。(请注意,使用的底层语言是 C++-compiled-to-wasm,但我认为这对于这个问题并不重要。)
我想一种可能性可能类似于:https://rreverser.com/webassembly-shell-with-a-real-filesystem-access-in-a-browser/。
解决方案
Javascript 文件 API
通过研究File API发现,在读取文件时,浏览器总是会处理一个Blob
. 这给人的印象是浏览器将所有文件都提取到 RAM 中。Blob
也有一个.stream()
函数,它返回一个相同的ReadableStream
流Blob
。
事实证明(至少在 中Chrome
)handledBlob
是虚拟的,并且在请求之前不会加载底层文件。文件对象切片和实例化读取器都不会加载整个文件:
file.slice(file.size - 100)
(await reader.read()).value.slice(0, 100)
该示例让您选择一个文件广告将显示最后 100 个字符(使用.slice()
)和前 100 个使用ReadableStream
(请注意,流函数没有seek
功能)
我已经测试了高达 10GB(.csv
我已经放置的最大)并且浏览器没有消耗 RAM
这回答了问题的第一部分。凭借在不消耗 RAM 的情况下流式传输(或执行分块访问)文件的能力,您可以使用任意大的文件并搜索您的内容(二进制搜索或表扫描)。
网络组装
在Rust
使用stdweb
中没有任何.read()
功能(因此无法流式传输内容)。但File
确实具有.slice()
切片底层 blob 的功能(与 javascript 中相同)。这是一个最小的工作示例:
#[macro_use]
extern crate stdweb;
use stdweb::js_export;
use std::convert::From;
use stdweb::web::IBlob;
use stdweb::web::File;
use stdweb::web::FileReader;
use stdweb::web::FileReaderResult;
#[js_export]
fn read_file(file: File) {
let blob = file.slice(..2048);
let len = stdweb::Number::from(blob.len() as f64);
js! {
var _len = @{len};
console.log("length=" + _len);
var _blob = @{blob};
console.log(_blob);
}
}
fn main() {
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WASM</title>
</head>
<body>
<input type="file" id="field" />
<script src="the_compiled_wasm_binding.js"></script>
<script>
async function onChange(e) {
const files = e.target.files;
if (files.length === 0) return;
const file = files[0];
// Slice
Rust.the_compiled_wasm_binding.then(module => {
module.read_file(file);
})
}
document.getElementById("field").onchange = onChange;
</script>
</body>
</html>
该.slice()
函数的行为与 javascript 中的相同(整个文件未加载到 RAM 中),因此您可以在 WASM 中加载文件的块并执行搜索。
请注意,内部执行使用的stdweb
实现:slice()
slice_blob()
js! (
return @{reference}.slice(@{start}, @{end}, @{content_type});
).try_into().unwrap()
正如你所看到的,它在底层使用了 javascript,所以这里没有优化。
结论
恕我直言,文件读取实现更有效,javascript
因为:
stdweb::File
APIjavascript
在引擎盖下使用 raw(因此速度不快)stdweb::File
比javascript
对应的功能更少(缺乏流媒体和其他功能)。
那么实际上搜索算法可以/应该在 WASM 中实现。该算法可以直接处理要处理的块(Blob
)。
推荐阅读
- c# - Azure Function - 如何启动 excel 文档
- r - 如何使用 gsub 在 for 循环中找到完全匹配?
- javascript - 如果站点从短信中的链接打开,则在触摸之前不会触发 beforeinstallprompt
- python - 如何从我抓取的网页中删除空格?
- react-native - 如何模拟从 npm 模块导入的类
- delphi - Delphi 10.3.2 - 将项目添加到版本控制 - 导入到 SVN 崩溃 Delphi IDE
- r - R Windows:安装包“rvest”(和其他几个包)时出错
- javascript - 如何处理nodejs应用程序中的“ReferenceError:require is not defined”?
- dask - 如何有效地加入多个 dask 数据帧
- sql - Oracle APEX - 将隐藏的 SQL 查询下载到 CSV