2 个稳定版本
使用旧的 Rust 2015
1.0.2 | 2018 年 9 月 23 日 |
---|
#726 在 图像
310KB
1.5K SLoC
Voronoi
这个小项目
- 在图像上随机选择一些点。
- 将每个像素分配给其中一个随机点(Voronoi)。
- 将属于一个点的所有像素用它们平均的颜色着色。
这只是一个小挑战
- 尽量通过使用复杂度良好的算法和避免低效来实现良好的性能。不过,没有使用GPU的高级数学。
- 使其尽可能类型安全。
示例
原始图像以及 Voronoi 颜色化(在 L1(曼哈顿),L2(欧几里得)和 L3 范数下)
性能
- 有很多包装类型用于安全性,但这些在Rust中不应产生任何成本。
- 尽量避免堆分配。图像本身、中心点列表和像素映射应该堆分配,尽管它们的尺寸在编译时是未知的。
- 尽量完全避免动态分派。
- 避免不必要的数学,例如,L1/L2/L3 范数值在平方/立方根之前具有相同的总顺序。
- 避免不必要的分配,即每个线程都保留一个向量以存储最近的点。
- 将像素分配给中心点并着色像素可以在没有锁的情况下按行并行化。平均颜色比较困难,因为平均颜色是可变共享的。
上述 Voronoi 变换,在小图像上每个补丁有 500 个像素,在我的机器上总共花费 0.02 秒到 0.05 秒。这主要是加载和保存数据以及启动线程池 - 没有这些,它只需要 0.0075 秒或每秒 133 倍。剥离的二进制文件为 1.7MB。
类型
有几个考虑因素用于类型安全
- 像素以正整数在两个维度上索引。我们不应该混合这些维度或比较不同点的 x 和 y。
- 对图像的有限区域进行迭代。因此需要总排序以及加法和减法。
- 像素位置的总和没有意义,但可以将它们相加以计算中点位置。但它们可以相减,这给出了一个可以负数的距离 - 与索引本身不同。
- 距离不是实数,也不关心方向。
- 由于多态性的原因,不同的规范应返回相同或兼容的类型,尽管它们的物理单位不同,因为跳过了`sqrt`。
- 为大多数集合创建了包装类型,以防止错误访问。
困难
- 一些操作不能通用地重载,但必须因为孤儿规则而为每个具体类型重新实现。宏在这里有所帮助。
- 有必要公开`usize`数据,例如生成随机数或构建`Vec`。
- 用于类型及其操作的代码比“业务”逻辑要多得多。
类型安全和测试是有效的:一旦编译并通过所有测试,程序就会立即运行。然而,它比缓慢且简单的Python版本要慢一个数量级以上。
如何使用
安装
cargo install voronoiify-image
最简单的用法是 voronoi my/img/path.png。
可以在 voronoi --help 中找到一些自定义选项。
> Voronoiify > 将图像分组到基于Voronoi的补丁并分配每个补丁的平均颜色 > > 用法: > voronoi [FLAGS] [OPTIONS] <IN_PTH> > > 标志: > -h, --help 打印帮助信息 > -s, --show 使用EOG显示生成的图像 > -V, --version 打印版本信息 > > 选项: > -o, --output <OUT_PTH> 存储生成文件的路径 > -r, --seed <SEED> 介于0和2^64(不包括)之间的随机种子 > -c, --patch_size <PATCH_SIZE> 每组的平均像素数 > > 参数: > <IN_PTH> 输入png文件以进行voronoiify
该包是稳定的,但功能不是很丰富。即它仅与没有alpha通道的png图像进行测试,并且使用EOG显示图像。欢迎提交拉取请求!
开发使用
- 编译: RUSTFLAGS="-C target-cpu=native" cargo build --release --bin voronoi-benchmark --bin voronoi
- 减小大小: strip target/release/voronoi target/release/voronoi-benchmark
- 运行: target/release/voronoi resources/imgs/parrots.png --show
- 基准测试: time target/release/voronoi-benchmark
随机观察/提示
- 几乎所有内容在-O3下都会内联,因此火焰图不起作用。
- 要查看derives生成的代码,请使用 cargo rustc -- -Z unstable-options --pretty=expanded。
- 由于某种原因,-C link-dead-code似乎可以提高2-3%的性能,而不会增加大小。
- 要分析,请使用 valgrind --tool=callgrind --dump-instr=yes --collect-jumps=yes --simulate-cache=yes,但请注意,很多内容都会内联。
- 在每次迭代中分配一个中心链接向量似乎比重复回收相同的向量要快。
- 删除所有显式内联略微提高了性能。
依赖关系
~13MB
~91K SLoC