mapbox / mapbox/node-cpp-skel

Enhancement: pass data from node::Buffer into threadpool

未关闭
#67 0 条评论 2 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

help wanted
主要语言
C++
星标
74
派生
9
PR 合并指标
30 天内没有已合并 PR

描述

## Context

The primary goal of many node C++ addons is to provide efficient and concurrent operations on data such that the total latency under high load will be reduced compared to a pure JS implementation.

This goal is achieved when the time taken to pass data into the threadpool, compute the operation, and return data/results from the threadpool is either:

- 🍍 Faster than pure JS compute (when we compare 1 JS operation with 1 C++ operation); or
- 🍊 Has less cost than the cost of the JS main loop being blocked when running the compute in pure JS and therefore results in lower latency overall. In other words, where the main bottleneck of the program was main event loop blocking (more work could be done by an available CPU but because of the synchronous nature of the event loop the CPU was not fully utilized).

The 🍊 scenario is the most common reason to move processing to C++. This means that 🍊 makes sense as a key way to reduce your program latency even if the time of a single computation is slower in C++. The highest aim is to achieve both 🍍 and 🍊, but the latter is the most important.

To recap:

Port to C++ when:

- you have evidence your event loop is blocked on computation
- you have evidence your CPU(s) are underutilized

Deploy your C++ when:

- your C++ port of the compute runs raster (comparing one C++ call to one JS call), or;
- your latency drops because the event loop is unblocked and is able to do other work while your C++ compute runs async
- and ideally both.

_Note: Another goal of some node C++ addons is simply to wrap/make available some existing library in C++ for nodejs users and. For this case performance and concurrency are not often motivating factors to the design. An example of this is node-gdal, which [currently only supports sync functions](https://github.com/naturalatlas/node-gdal/issues/18). An example of a node module that both wraps an existing C++ library to expose functionality and aims for optimally efficient and async functions is [node-mapnik](https://github.com/mapnik/node-mapnik)_

## Improvements to skel

Currently node-cpp-skel examples only show code passing a `std::string` into the threadpool that represents a "name" (or a short string of user provided input). The C++ `std::string` can be a good transport format for passing data into the threadpool. And of course you might have an addon that explicitly needs to do compute on strings. But the more common use case I've seen and that I think we should target is using `node::Buffer`s.

The reason is that, using `node::Buffer` we can pass data directly from JS into the C++ threadpool in a zero-copy way (no copies of the data inside the `node::Buffer` will need to be made to access it). We can't do this with `std::string` because we don't get a `std::string` from the JS side.

You might ask: why can't we use a `v8::String` (which is what we get from the JS side if a string is passed by the user)? Well, we could, but a `v8::String` still needs to be copied before entering the threadpool and reading out the data for this copy can be costly. This is because `v8::String` is implemented in a somewhat lazy (do work only when needed) kind of way in V8. For example, a given string might actually be a combination of a bunch of strings (aka a `ConsString` in v8), and to use its data and know the length v8 needs to "flatten" the string first. This takes time and allocations, while the `node::Buffer` is able to tell us its length and give us access to its raw data more efficiently.

## Recommended changes

- [ ] Rework the async examples to take a `node::Buffer` object
- [ ] Add structures to safely pass its data into the threadpool
- [ ] Change the `do_expensive_work` to do work on the buffers data

The node::Buffer object can be safely used in the threadpool by:

- Adding a `Nan::Persistent buffer;` to the "baton" (aka the `class` that inherits from `Nan::AsyncWorker`). This prevents the buffer from being garbage collected while in use. This is needed because the JS code passing the buffer might not use the buffer again and then `v8` would not otherwise know to keep it alive.
- Adding `const char * data;` to the "baton" which takes a pointer to the raw data inside the `node::Buffer`
- Adding a `std::size_t data_length;` to the "baton" which stores a copy of the length (to be used to know its length later on, if needed).

_Note: reader may notice that passing both a `data` and a `data_length` is clumsy and the two could get out of sync. This is true. Ideally there were a nice container to store them both without copying the `data` like creating a `std::string` would. There is just this container in the upcoming C++17 implementation of `string_view` and until that happens [this class from protozero that can be used](https://github.com/mapbox/protozero/blob/5ad35fd13a067b3b3e113399dd44dbf986624749/include/protozero/types.hpp#L69-L199) or [boost string_ref](http://www.boost.org/doc/libs/1_59_0/libs/utility/doc/html/string_ref.html) for boost users._

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

调研方向

首先定位 issue 中引用的异步示例和 do_expensive_work 实现。重做这些示例,使其接受一个 node::Buffer,为 threadpool 工作安全地保留其数据和长度,并让 do_expensive_work 对这些数据进行操作;建议的更改清单定义了完成标准。

由索引模型根据 Issue 内容生成。

评估

技术栈
cpp, node.js
领域
developer-experience, tooling
Issue 类型
功能
难度
3/5
预计耗时
1-2 天
活跃度
停滞
描述清晰度
基本清楚
新手友好度
42/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。