Set과 Map에 모으기

Set과 Map에 모으기

JavaScript의 Set과 Map을 받는 커널 함수 네 개와, 이를 부르는 호스트입니다. labels는 영이 아닌 라벨을 한 번씩 넣고, latest는 키마다 마지막에 넣은 값을 남깁니다. lookup은 has로 확인한 뒤 get으로 값을 읽고, present는 한 루프에서 값을 넣고 다음 루프에서 읽습니다. 호스트는 new Set()과 new Map()을 넘깁니다. 각 프라미스가 이행되면 그 안에는 루프를 차례로 실행했을 때 남는 항목이 들어 있습니다. 키는 처음 넣은 순서대로 한 번씩 들어가고, 값은 마지막에 넣은 것입니다. WebGPU에서는 add나 set마다 레코드를 덧붙이고 호출이 이를 순서대로 적용합니다. 조회는 업로드한 키를 이진 탐색합니다. WebGL2는 지원하지 않습니다.

실행 tier WebGPUCPU tier

이 목록은 컴파일러가 확인합니다. tier(호출이 실행되는 백엔드)는 호스트의 실행 전체가 그 tier 하나만으로 기준값을 낼 때만 목록에 오릅니다. 컴파일러의 저니 게이트가 호스트마다 WebGPU와 WebGL2에서 실행하고, 단위 테스트가 CPU tier에서 실행합니다.

"use typeshade";
/* @example
{
"title": "Host: collect into a Set and a Map",
"blurb": "Four kernel functions that take JavaScript's own `Set` and `Map` (change 0080), and the host that calls them. `labels` adds each nonzero label once, `latest` keeps the last value set for each key, `lookup` reads the map where `has` guards `get`, and `present` adds in one loop and reads in the next. The host passes `new Set()` and `new Map()`, and when each promise resolves they hold the entries a sequential run leaves: each key once, in the order first added, the value set last. On WebGPU each add or set pushes a record the call applies in order, and a lookup is a binary search of the keys the call uploads; WebGL2 runs none.",
"renderable": false,
"reason": "no entry point"
}
*/
// Each nonzero label once, in the order first seen. The function returns how many there are.
export function labels(pixels: array<u32>, seen: Set<u32>): u32 {
for (let i: u32 = 0; i < pixels.length; i++) {
if (pixels[i] !== 0) {
seen.add(pixels[i]);
}
}
return seen.size;
}
// The last value set for each key, in the place of the key's first set.
export function latest(keys: array<u32>, values: array<f32>, last: Map<u32, f32>) {
for (let i: u32 = 0; i < keys.length; i++) {
last.set(keys[i], values[i]);
}
}
// One call adds in its first loop and reads in its second: each probe is 1 when an earlier pixel
// of the image holds its label, 0 otherwise.
export function present(pixels: array<u32>, seen: Set<u32>, probes: array<u32>, out: array<u32>) {
for (let i: u32 = 0; i < pixels.length; i++) {
seen.add(pixels[i]);
}
for (let i: u32 = 0; i < probes.length; i++) {
out[i] = seen.has(probes[i]) ? 1 : 0;
}
}
// A later call reads the map: a key it does not hold reads as -1.
export function lookup(keys: array<u32>, last: Map<u32, f32>, out: array<f32>) {
for (let i: u32 = 0; i < keys.length; i++) {
out[i] = last.has(keys[i]) ? last.get(keys[i])! : -1.;
}
}
// A closed set of string keys uses ids on the GPU and strings on the host (change 0081).
type Channel = "r" | "g" | "b";
export function channels(xs: array<f32>, seen: Set<Channel>, last: Map<Channel, f32>, out: array<f32>): u32 {
for (let i: u32 = 0; i < xs.length; i++) {
const c: Channel = i % 3 === 0 ? "g" : i % 3 === 1 ? "r" : "b";
seen.add(c);
}
for (let i: u32 = 0; i < xs.length; i++) {
const c: Channel = i % 3 === 0 ? "g" : i % 3 === 1 ? "r" : "b";
last.set(c, xs[i]);
}
for (let i: u32 = 0; i < out.length; i++) {
const c: Channel = i % 3 === 0 ? "g" : i % 3 === 1 ? "r" : "b";
out[i] = seen.has(c) && last.has(c) ? last.get(c)! : -1.;
}
return seen.size;
}
// Keys used as Map values also cross the boundary as strings, including an initial lookup.
export function channelNames(xs: array<f32>, names: Map<Channel, Channel>, out: array<u32>) {
for (let i: u32 = 0; i < xs.length; i++) {
names.set("g", "r");
names.set("b", "g");
}
for (let i: u32 = 0; i < out.length; i++) {
out[i] = u32(names.has("r") && names.get("r")! === "b" ? 1 : 0) + u32(names.has("g") && names.get("g")! === "r" ? 2 : 0);
}
}
// Collect into JavaScript's own Set and Map from kernel functions (surface §74, change 0080).
//
// The host passes a `Set` and a `Map` as it passes an array, by reference. When the promise
// resolves they hold the entries a sequential run of the loop leaves, in insertion order.
import {
channelNames,
channels,
labels,
latest,
lookup,
present,
} from './host-set-unique.shade.ts';
/** The unique nonzero labels of a small image, the last weight set for each label, and the
* weights of labels 0 to 9 read back from the map, -1 for a label it does not hold. */
export async function run(n = 64): Promise<{
seen: Set<number>;
count: number;
last: Map<number, number>;
found: Float32Array;
present: Uint32Array;
names: Map<string, string>;
nameMarks: Uint32Array;
channels: { seen: Set<string>; last: Map<string, number>; found: Float32Array; count: number };
}> {
// Labels 0 to 6, where 0 is the background.
const pixels = Uint32Array.from({ length: n }, (_, i) => (i * 5) % 7);
const seen = new Set<number>();
const count = await labels(pixels, seen); // seen holds 5, 3, 1, 6, 4, 2
const weights = Float32Array.from({ length: n }, (_, i) => i / 4);
const last = new Map<number, number>();
await latest(pixels, weights, last); // the weight of each label's last pixel
const found = new Float32Array(10);
await lookup(
Uint32Array.from({ length: 10 }, (_, i) => i),
last,
found,
);
// One call that adds, then reads what it added: labels 0 to 9 against the image's.
const marks = new Uint32Array(10);
await present(
pixels,
new Set<number>(),
Uint32Array.from({ length: 10 }, (_, i) => i),
marks,
);
const channelSeen = new Set<'r' | 'g' | 'b'>(['r']);
const channelLast = new Map<'r' | 'g' | 'b', number>([
['b', -1],
['r', -2],
]);
const channelFound = new Float32Array(6);
const channelCount = await channels(weights, channelSeen, channelLast, channelFound);
const names = new Map<'r' | 'g' | 'b', 'r' | 'g' | 'b'>([['r', 'b']]);
const nameMarks = new Uint32Array(3);
await channelNames(weights, names, nameMarks);
return {
names,
nameMarks,
seen,
count,
last,
found,
present: marks,
channels: { seen: channelSeen, last: channelLast, found: channelFound, count: channelCount },
};
}

두 파일 모두 커밋 91c2fe31의 컴파일러에 있는 그대로이며, 예제 디렉터리에서 읽어 왔습니다.

GitHub의 모듈GitHub의 호스트 파일

이 페이지 편집 문제 보고