首页 > 解决方案 > 使用 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/

标签: c++google-chromerustwebassemblyemscripten

解决方案


Javascript 文件 API

通过研究File API发现,在读取文件时,浏览器总是会处理一个Blob. 这给人的印象是浏览器将所有文件都提取到 RAM 中。Blob也有一个.stream()函数,它返回一个相同的ReadableStreamBlob

事实证明(至少在 中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::FileAPIjavascript在引擎盖下使用 raw(因此速度不快)
  • stdweb::Filejavascript对应的功能更少(缺乏流媒体和其他功能)。

那么实际上搜索算法可以/应该在 WASM 中实现。该算法可以直接处理要处理的块(Blob)。


推荐阅读