![](/img/trans.png)
[英]Looking for an efficient way to index 2D tensor by another 2D tensor in pytorch
[英]More efficient way to iterate through columns of flattened 2D Vec
所以我有一個Vec<bool>
存儲在self.board.data
中,代表一個二維網格。 逐行遍歷它並在其索引和 x,y 坐標之間進行轉換很容易,但是要逐列對其進行索引,我需要使用嵌套for
循環。 有沒有更有效的方法來做到這一點? 我有類似於這個的函數,會運行很多次,所以我想讓它們盡可能高效。
///returns average height of all columns
fn calc_avg_height(&self) -> f32 {
let mut heights = 0;
for x in 0..self.board.width {
for y in 0..self.board.height {
if self.board.data[(y*self.board.width)+x] {
heights+=self.board.height-y;
break
}
}
}
heights as f32/self.board.width as f32
}
無論您做什么,逐列讀取數據都會很慢,因為您將跳入 memory。與此相比,計算索引是噪音。
有沒有更有效的方法來做到這一點?
一個想法是將位放在一起以將 memory 減少 8。另一個想法是逐列保存數據,並且僅在需要時更新(這取決於您的訪問模式)。
您可以預加載索引,這樣您只需要遍歷它們即可。 這可能在時間上更有效率但在空間上不是:
fn preload_indexes(row_size: usize, column_size: usize) -> Vec<usize> {
let mut indexes = Vec::new();
for x in 0..row_size {
for y in 0..column_size {
let i = y*row_size+x;
indexes.push(i);
}
}
indexes
}
作為一個完整的例子:
use std::fmt::Display;
struct Matrix<T: Display> {
row_size: usize,
column_size: usize,
data: Vec<T>,
columns_indexes: Vec<usize>,
}
fn preload_indexes(row_size: usize, column_size: usize) -> Vec<usize> {
let mut indexes = Vec::new();
for x in 0..row_size {
for y in 0..column_size {
let i = y*row_size+x;
indexes.push(i);
}
}
indexes
}
impl<T: Display> Matrix<T> {
fn new(data: Vec<T>, row_size: usize, column_size: usize) -> Self {
assert_eq!(data.len(), row_size*column_size);
Self {
row_size,
column_size,
data,
columns_indexes: preload_indexes(row_size, column_size),
}
}
fn print_rows(&self) {
for e in self.data.iter() {
println!("{}", e);
}
}
fn print_columns(&self) {
for i in self.columns_indexes.iter() {
println!("{}", self.data[*i]);
}
}
}
fn main() {
let matrix = Matrix::new(vec![1, 2, 3, 4], 2, 2);
println!("Rows");
matrix.print_rows();
println!("Columns");
matrix.print_columns();
}
圍繞你的例子,我很驚訝地發現索引計算( y*width+x
)似乎沒有被優化器簡化。 此外,我認為 memory 訪問比索引計算中刪除的乘法更重要。 但是當談到時間時,我可以看到很大的不同(不同的網格大小和真/假初始化)。
編輯
閱讀下面的一些評論后,我在編譯器資源管理器 (godbolt) 中比較了兩個版本_v1
和_v2
。 除了_v1
使用lea
指令而_v2
使用add
和inc
之外,它們看起來非常相似。 根據 vtune-amplifier, _v1
的大部分時間都花在了這個lea
指令上。 因此,至少在我的計算機上,在這個特定示例中,與直覺相反,選擇lea
而不是add
和inc
似乎比不規則的 memory 訪問模式更有害。
/*
$ rustc -C opt-level=3 prog.rs && ./prog
avg1=50.5
v1: 4453 ms
avg2=50.5
v2: 2361 ms
*/
pub struct Board {
width: usize,
height: usize,
data: Vec<bool>,
}
impl Board {
pub fn new(
width: usize,
height: usize,
) -> Self {
Self {
width,
height,
data: vec![false; width * height],
}
}
pub fn calc_avg_height_v1(&self) -> f32 {
let mut heights = 0;
for x in 0..self.width {
for y in 0..self.height {
if self.data[(y * self.width) + x] {
heights += self.height - y;
break;
}
}
}
heights as f32 / self.width as f32
}
pub fn calc_avg_height_v2(&self) -> f32 {
let mut heights = 0;
for x in 0..self.width {
let mut idx = x;
for y in 0..self.height {
if self.data[idx] {
heights += self.height - y;
break;
}
idx += self.width;
}
}
heights as f32 / self.width as f32
}
}
pub fn run<F>(
name: &str,
repeat: usize,
fnct: F,
) -> u128
where
F: Fn() -> f32,
{
let mut prev = -1.0_f32;
let warmup = repeat / 10;
for _ in 0..warmup {
let avg = fnct();
if avg != prev {
println!("{}={}", name, avg);
prev = avg;
}
}
let now = std::time::Instant::now();
for _ in 0..repeat {
let avg = fnct();
if avg != prev {
println!("{}={}", name, avg);
prev = avg;
}
}
now.elapsed().as_millis()
}
pub fn main() {
let mut board = Board::new(100, 100);
for y in 0..board.height {
for x in 0..board.width {
board.data[y * board.width + x] = x == y;
}
}
let repeat = 1_000_000;
println!(
" v1: {} ms",
run("avg1", repeat, || board.calc_avg_height_v1())
);
println!(
" v2: {} ms",
run("avg2", repeat, || board.calc_avg_height_v2())
);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.