wasm凸包测试

This commit is contained in:
kura 2024-12-17 17:52:09 +08:00
parent e811c04eeb
commit 75c3e31bc4
8 changed files with 319 additions and 2889 deletions

View File

@ -1,42 +1,76 @@
#include <stdlib.h> #include <stdlib.h>
// 定义一个点结构 // 定义点结构
typedef struct { typedef struct {
int x, y; int x;
int y;
} Point; } Point;
// 计算两点的极角 // 计算叉积
int orientation(Point p, Point q, Point r) { double crossProduct(Point p0, Point p1, Point p2) {
int val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);
if (val == 0) return 0; // 共线
return (val > 0) ? 1 : 2; // 顺时针或逆时针
} }
// 对点的极角排序 // 计算两点间距离的平方
int compare(const void *vp1, const void *vp2) { double distanceSquare(Point p1, Point p2) {
Point *p1 = (Point *)vp1; return (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y);
Point *p2 = (Point *)vp2;
return (p1->y < p2->y) || (p1->y == p2->y && p1->x < p2->x);
} }
// 凸包主函数 // 比较函数
void convexHull(Point points[], int n, Point result[], int *result_size) { Point p0;
// 如果点数小于 3无法构成凸包 int comparePoints(const void* vp1, const void* vp2) {
Point* p1 = (Point*)vp1;
Point* p2 = (Point*)vp2;
double cross = crossProduct(p0, *p1, *p2);
if (cross == 0) {
return (distanceSquare(p0, *p1) - distanceSquare(p0, *p2));
}
return (cross < 0) ? 1 : -1;
}
// 导出的凸包计算函数
void convexHull(Point* points, int n, Point* result, int* resultSize) {
if (n < 3) { if (n < 3) {
*result_size = 0; // 点数小于3直接复制所有点
for (int i = 0; i < n; i++) {
result[i] = points[i];
}
*resultSize = n;
return; return;
} }
// 按 y 坐标排序,如果 y 坐标相同则按 x 坐标排序 // 找到y坐标最小的点
qsort(points, n, sizeof(Point), compare); int min = 0;
for (int i = 1; i < n; i++) {
if (points[i].y < points[min].y ||
(points[i].y == points[min].y && points[i].x < points[min].x)) {
min = i;
}
}
// 将最下方的点交换到points[0]
Point temp = points[0];
points[0] = points[min];
points[min] = temp;
// 保存最下方的点用于排序
p0 = points[0];
// 按极角排序
qsort(&points[1], n-1, sizeof(Point), comparePoints);
// 构建凸包 // 构建凸包
int m = 0; // 凸包的点数 int m = 0;
for (int i = 0; i < n; i++) { result[m++] = points[0];
while (m >= 2 && orientation(result[m-2], result[m-1], points[i]) != 2) result[m++] = points[1];
result[m++] = points[2];
for (int i = 3; i < n; i++) {
while (m > 1 && crossProduct(result[m-2], result[m-1], points[i]) <= 0) {
m--; m--;
}
result[m++] = points[i]; result[m++] = points[i];
} }
*result_size = m; *resultSize = m;
} }

227
convex_hull.html Normal file
View File

@ -0,0 +1,227 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>凸包算法</title>
</head>
<body>
<textarea style="width: 100%;height: 100px;" id="points" placeholder="输入点格式x1,y1;x2,y2;x3,y3;..."></textarea>
<button onclick="convex_hull()">计算凸包</button>
<button onclick="randomPointsToTextarea(30)">随机30点</button>
<button onclick="randomPointsToTextarea(3000)">随机3000点</button>
<button onclick="randomPointsToTextarea(10000)">随机10000点</button>
<button onclick="randomPointsToTextarea(30000)">随机30000点</button>
<div id="result"></div>
<canvas id="canvas" width="800" height="800"></canvas>
</body>
<script src="./convex_hull.js"></script>
<script>
function callWasmConvexHull(points) {
//长度
var n = points.length;
var pointsPtr = Module._malloc(n * 8); //8字节一个点
for (let i = 0; i < n; i++) {
Module.setValue(pointsPtr + i * 8, points[i].x, 'i32');
Module.setValue(pointsPtr + i * 8 + 4, points[i].y, 'i32');
}
//多少点
var resultPtr = Module._malloc(n * 8);
var resultSizePtr = Module._malloc(4);
console.time('wasm耗时')
Module._convexHull(pointsPtr, n, resultPtr, resultSizePtr);
console.timeEnd('wasm耗时')
var resultSize = Module.getValue(resultSizePtr, 'i32');
var resultPoints = [];
//读
for (let i = 0; i < resultSize; i++) {
var x = Module.getValue(resultPtr + i * 8, 'i32');
var y = Module.getValue(resultPtr + i * 8 + 4, 'i32');
resultPoints.push({ x: x, y: y });
}
// 释放
Module._free(pointsPtr);
Module._free(resultPtr);
Module._free(resultSizePtr);
return resultPoints
}
function randomPointsToTextarea(n) {
var points = Array.from({ length: n }, () => ({
x: Math.floor(Math.random() * 100),
y: Math.floor(Math.random() * 100)
}));
document.getElementById('points').value = points.map(p => `${p.x},${p.y}`).join(';');
draw(points)
}
//计算凸包
function convex_hull(points) {
var points = document.getElementById('points').value.split(';').map(item => {
var [x, y] = item.split(',');
return { x: parseInt(x), y: parseInt(y) };
});
console.log('--------------------------------')
//wasm整体调用计时
console.time('wasm整体耗时,包含指针传递')
var resultPoints = callWasmConvexHull(points)
console.timeEnd('wasm整体耗时,包含指针传递')
document.getElementById('result').innerHTML = JSON.stringify(resultPoints)
drawConvexHull(resultPoints)
//js实现凸包
console.time('js调用')
var resultPoints = jsConvexHull(points)
console.timeEnd('js调用')
drawConvexHull(resultPoints)
}
let padding = 100; // 增加内边距,让点不贴边
let minX = 0;
let minY = 0;
let maxX = 0;
let maxY = 0;
let scale = 1;
// 转换坐标
function transformPoint(p, canvas) {
return {
x: padding + (p.x - minX) * scale,
y: canvas.height - padding - (p.y - minY) * scale
};
}
/**
* canvas画点
*/
function draw(points) {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 找出所有点的最大最小值
minX = Math.min(...points.map(p => p.x));
maxX = Math.max(...points.map(p => p.x));
minY = Math.min(...points.map(p => p.y));
maxY = Math.max(...points.map(p => p.y));
// 计算缩放比例
var scaleX = (canvas.width - 2 * padding) / (maxX - minX || 1);
var scaleY = (canvas.height - 2 * padding) / (maxY - minY || 1);
scale = Math.min(scaleX, scaleY);
// 绘制点
var transformedPoints = points.map(p => transformPoint(p, canvas));
ctx.fillStyle = 'blue';
transformedPoints.forEach(point => {
ctx.beginPath();
ctx.arc(point.x, point.y, 4, 0, Math.PI * 2); // 绘制半径为4的圆点
ctx.fill();
});
}
/**
* 画凸包
*/
function drawConvexHull(points) {
// 使用相同的绘制函数,但使用不同的颜色
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// 绘制凸包
ctx.beginPath();
ctx.strokeStyle = 'red';
var transformedPoints = points.map(p => transformPoint(p, canvas));
ctx.moveTo(transformedPoints[0].x, transformedPoints[0].y);
for (let i = 1; i < transformedPoints.length; i++) {
ctx.lineTo(transformedPoints[i].x, transformedPoints[i].y);
}
// 闭合凸包
ctx.lineTo(transformedPoints[0].x, transformedPoints[0].y);
ctx.stroke();
}
//loadwasm
function loadwasm() {
var wasm_url = "./convex_hull.js";
var script = document.createElement('script');
script.src = wasm_url;
script.onload = function () {
//loadwasm
console.log(Module)
Module.onRuntimeInitialized = function () {
// convex_hull()
}
}
document.body.appendChild(script);
}
loadwasm()
randomPointsToTextarea(30)
//js实现凸包 不使用wasm
function jsConvexHull(points) {
// 如果点少于3个直接返回原始点集
if (points.length < 3) return points;
// 找到最低且最左的点作为起始点
let start = points.reduce((lowest, point) => {
if (point.y < lowest.y || (point.y === lowest.y && point.x < lowest.x)) {
return point;
}
return lowest;
}, points[0]);
// 计算其他点相对于起始点的极角,并排序
let sorted = points
.filter(point => point !== start)
.map(point => ({
point: point,
angle: Math.atan2(point.y - start.y, point.x - start.x),
distance: Math.sqrt(Math.pow(point.x - start.x, 2) + Math.pow(point.y - start.y, 2))
}))
.sort((a, b) => {
if (a.angle === b.angle) {
return a.distance - b.distance;
}
return a.angle - b.angle;
})
.map(item => item.point);
// 将起始点加入结果数组
sorted.unshift(start);
// Graham扫描
let stack = [sorted[0], sorted[1]];
for (let i = 2; i < sorted.length; i++) {
while (stack.length >= 2 && !isCounterClockwise(
stack[stack.length - 2],
stack[stack.length - 1],
sorted[i]
)) {
stack.pop();
}
stack.push(sorted[i]);
}
return stack;
}
// 判断三个点是否形成逆时针方向
function isCounterClockwise(p1, p2, p3) {
return (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x) > 0;
}
</script>

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -1,196 +0,0 @@
/**
* @license
* Copyright 2015 The Emscripten Authors
* SPDX-License-Identifier: MIT
*/
// Pthread Web Worker startup routine:
// This is the entry point file that is loaded first by each Web Worker
// that executes pthreads on the Emscripten application.
'use strict';
var Module = {};
// Node.js support
var ENVIRONMENT_IS_NODE = typeof process == 'object' && typeof process.versions == 'object' && typeof process.versions.node == 'string';
if (ENVIRONMENT_IS_NODE) {
// Create as web-worker-like an environment as we can.
// See the parallel code in shell.js, but here we don't need the condition on
// multi-environment builds, as we do not have the need to interact with the
// modularization logic as shell.js must (see link.py:node_es6_imports and
// how that is used in link.py).
var nodeWorkerThreads = require('worker_threads');
var parentPort = nodeWorkerThreads.parentPort;
parentPort.on('message', (data) => onmessage({ data: data }));
var fs = require('fs');
var vm = require('vm');
Object.assign(global, {
self: global,
require,
Module,
location: {
// __filename is undefined in ES6 modules, and import.meta.url only in ES6
// modules.
href: __filename
},
Worker: nodeWorkerThreads.Worker,
importScripts: (f) => vm.runInThisContext(fs.readFileSync(f, 'utf8'), {filename: f}),
postMessage: (msg) => parentPort.postMessage(msg),
performance: global.performance || { now: Date.now },
});
}
// Thread-local guard variable for one-time init of the JS state
var initializedJS = false;
function assert(condition, text) {
if (!condition) abort('Assertion failed: ' + text);
}
function threadPrintErr(...args) {
var text = args.join(' ');
// See https://github.com/emscripten-core/emscripten/issues/14804
if (ENVIRONMENT_IS_NODE) {
fs.writeSync(2, text + '\n');
return;
}
console.error(text);
}
function threadAlert(...args) {
var text = args.join(' ');
postMessage({cmd: 'alert', text, threadId: Module['_pthread_self']()});
}
// We don't need out() for now, but may need to add it if we want to use it
// here. Or, if this code all moves into the main JS, that problem will go
// away. (For now, adding it here increases code size for no benefit.)
var out = () => { throw 'out() is not defined in worker.js.'; }
var err = threadPrintErr;
self.alert = threadAlert;
var dbg = threadPrintErr;
Module['instantiateWasm'] = (info, receiveInstance) => {
// Instantiate from the module posted from the main thread.
// We can just use sync instantiation in the worker.
var module = Module['wasmModule'];
// We don't need the module anymore; new threads will be spawned from the main thread.
Module['wasmModule'] = null;
var instance = new WebAssembly.Instance(module, info);
// TODO: Due to Closure regression https://github.com/google/closure-compiler/issues/3193,
// the above line no longer optimizes out down to the following line.
// When the regression is fixed, we can remove this if/else.
return receiveInstance(instance);
}
// Turn unhandled rejected promises into errors so that the main thread will be
// notified about them.
self.onunhandledrejection = (e) => {
throw e.reason || e;
};
function handleMessage(e) {
try {
if (e.data.cmd === 'load') { // Preload command that is called once per worker to parse and load the Emscripten code.
// Until we initialize the runtime, queue up any further incoming messages.
let messageQueue = [];
self.onmessage = (e) => messageQueue.push(e);
// And add a callback for when the runtime is initialized.
self.startWorker = (instance) => {
// Notify the main thread that this thread has loaded.
postMessage({ 'cmd': 'loaded' });
// Process any messages that were queued before the thread was ready.
for (let msg of messageQueue) {
handleMessage(msg);
}
// Restore the real message handler.
self.onmessage = handleMessage;
};
// Module and memory were sent from main thread
Module['wasmModule'] = e.data.wasmModule;
// Use `const` here to ensure that the variable is scoped only to
// that iteration, allowing safe reference from a closure.
for (const handler of e.data.handlers) {
Module[handler] = (...args) => {
postMessage({ cmd: 'callHandler', handler, args: args });
}
}
Module['wasmMemory'] = e.data.wasmMemory;
Module['buffer'] = Module['wasmMemory'].buffer;
Module['workerID'] = e.data.workerID;
Module['ENVIRONMENT_IS_PTHREAD'] = true;
if (typeof e.data.urlOrBlob == 'string') {
importScripts(e.data.urlOrBlob);
} else {
var objectUrl = URL.createObjectURL(e.data.urlOrBlob);
importScripts(objectUrl);
URL.revokeObjectURL(objectUrl);
}
} else if (e.data.cmd === 'run') {
// Pass the thread address to wasm to store it for fast access.
Module['__emscripten_thread_init'](e.data.pthread_ptr, /*is_main=*/0, /*is_runtime=*/0, /*can_block=*/1);
// Await mailbox notifications with `Atomics.waitAsync` so we can start
// using the fast `Atomics.notify` notification path.
Module['__emscripten_thread_mailbox_await'](e.data.pthread_ptr);
assert(e.data.pthread_ptr);
// Also call inside JS module to set up the stack frame for this pthread in JS module scope
Module['establishStackSpace']();
Module['PThread'].receiveObjectTransfer(e.data);
Module['PThread'].threadInitTLS();
if (!initializedJS) {
initializedJS = true;
}
try {
Module['invokeEntryPoint'](e.data.start_routine, e.data.arg);
} catch(ex) {
if (ex != 'unwind') {
// The pthread "crashed". Do not call `_emscripten_thread_exit` (which
// would make this thread joinable). Instead, re-throw the exception
// and let the top level handler propagate it back to the main thread.
throw ex;
}
}
} else if (e.data.cmd === 'cancel') { // Main thread is asking for a pthread_cancel() on this thread.
if (Module['_pthread_self']()) {
Module['__emscripten_thread_exit'](-1);
}
} else if (e.data.target === 'setimmediate') {
// no-op
} else if (e.data.cmd === 'checkMailbox') {
if (initializedJS) {
Module['checkMailbox']();
}
} else if (e.data.cmd) {
// The received message looks like something that should be handled by this message
// handler, (since there is a e.data.cmd field present), but is not one of the
// recognized commands:
err(`worker.js received unknown command ${e.data.cmd}`);
err(e.data);
}
} catch(ex) {
err(`worker.js onmessage() captured an uncaught exception: ${ex}`);
if (ex?.stack) err(ex.stack);
Module['__emscripten_thread_crashed']?.();
throw ex;
}
};
self.onmessage = handleMessage;

View File

@ -1 +1,32 @@
凸包算法的wasm实现 # 凸包算法的wasm实现
[在线测试地址](https://kuraa.cc/upload/convex_hull.html)
```js
function callWasmConvexHull(points) {
//长度
var n = points.length;
var pointsPtr = Module._malloc(n * 8); //8字节一个点
for (let i = 0; i < n; i++) {
Module.setValue(pointsPtr + i * 8, points[i].x, 'i32');
Module.setValue(pointsPtr + i * 8 + 4, points[i].y, 'i32');
}
//多少点
var resultPtr = Module._malloc(n * 8);
var resultSizePtr = Module._malloc(4);
//调用wasm
Module._convexHull(pointsPtr, n, resultPtr, resultSizePtr);
var resultSize = Module.getValue(resultSizePtr, 'i32');
var resultPoints = [];
//读
for (let i = 0; i < resultSize; i++) {
var x = Module.getValue(resultPtr + i * 8, 'i32');
var y = Module.getValue(resultPtr + i * 8 + 4, 'i32');
resultPoints.push({ x: x, y: y });
}
// 释放
Module._free(pointsPtr);
Module._free(resultPtr);
Module._free(resultSizePtr);
return resultPoints
}
```

View File

@ -1,54 +0,0 @@
<body></body>
<script>
//计算凸包
function convex_hull(points) {
var points = [
{ x: 0, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 2 },
{ x: 0, y: 3 }, { x: 3, y: 0 }, { x: 3, y: 3 },
{ x: 2, y: 1 }, { x: 1, y: 2 }
];
//长度
var n = points.length;
var pointsPtr = Module._malloc(n * 8); //8字节一个点
for (let i = 0; i < n; i++) {
Module.setValue(pointsPtr + i * 8, points[i].x, 'i32');
Module.setValue(pointsPtr + i * 8 + 4, points[i].y, 'i32');
}
//多少点
var resultPtr = Module._malloc(n * 8);
var resultSizePtr = Module._malloc(4);
Module._convexHull(pointsPtr, n, resultPtr, resultSizePtr);
var resultSize = Module.getValue(resultSizePtr, 'i32');
var resultPoints = [];
//读
for (let i = 0; i < resultSize; i++) {
var x = Module.getValue(resultPtr + i * 8, 'i32');
var y = Module.getValue(resultPtr + i * 8 + 4, 'i32');
resultPoints.push({ x: x, y: y });
}
console.log(resultPoints);
// 释放
Module._free(pointsPtr);
Module._free(resultPtr);
Module._free(resultSizePtr);
}
//loadwasm
function loadwasm() {
var wasm_url = "./convex_hull.js";
var script = document.createElement('script');
script.src = wasm_url;
script.onload = function () {
//loadwasm
console.log(Module)
Module.onRuntimeInitialized = function () {
convex_hull()
}
}
document.body.appendChild(script);
}
loadwasm()
</script>

3
生成
View File

@ -1,3 +1,4 @@
docker run --rm -v $(pwd):/src emscripten/emsdk emcc convex_hull.c -o convex_hull.js -s IMPORTED_MEMORY=1 -s INITIAL_MEMORY=104857600 -s EXPORTED_FUNCTIONS="['_convexHull','_malloc','_free','setValue','getValue']" -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' docker run --rm -v $(pwd):/src emscripten/emsdk emcc convex_hull.c -o convex_hull.js -s IMPORTED_MEMORY=1 -s INITIAL_MEMORY=104857600 -s EXPORTED_FUNCTIONS="['_convexHull','_malloc','_free','setValue','getValue']" -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]'
docker run --rm -v $(pwd):/src emscripten/emsdk emcc convex_hull.c -o convex_hull.js -s USE_PTHREADS=1 -s IMPORTED_MEMORY=1 -s EXPORTED_FUNCTIONS="['_convexHull','_malloc','_free','setValue','getValue']" -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' # 优化指令
docker run --rm -v $(pwd):/src emscripten/emsdk emcc convex_hull.c -o convex_hull.js -s IMPORTED_MEMORY=1 -s EXPORTED_FUNCTIONS="['_convexHull','_malloc','_free','setValue','getValue']" -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' -O2