Почему атрибут # [inline] перестает работать, когда функция перемещается в метод структуры? - PullRequest
1 голос
/ 07 августа 2020

У меня есть функция get_screen, которая указана в отдельном модуле из main.rs. Он берет два 2D-вектора (один с разрешением 1920x1080 и называется screen, а другой, который еще больше, называется world), и сопоставляет часть вектора world с вектором screen. Это сигнатура функции, когда я впервые ее сделал:

pub fn get_screen(
    screen: &mut Vec<Vec<[u8; 4]>>,
    world: &Vec<Vec<Chunk>>,
    camera_coords: (isize, isize),
    screen_width: usize,
    screen_height: usize,
    chunk_width: usize,
    chunk_height: usize,
)

У меня были серьезные проблемы со временем выполнения, но я оптимизировал его с 14 мс до 3 мс, используя #[inline].

I затем переместил вектор world в его собственную структуру (вместе с некоторыми другими связанными переменными, такими как ширина / высота блока) и превратил функцию get_screen в метод в новой структуре world. Вот как выглядела сигнатура функции после этого изменения:

pub fn get_screen(
    &self,
    screen: &mut Vec<Vec<[u8; 4]>>,
    camera_coords: (isize, isize),
    screen_width: usize,
    screen_height: usize,
)

Затем время выполнения увеличивается до 14 мс. Я пробовал включить lto=true в Car go .toml и переключиться на #[inline(always)], чтобы обеспечить его соблюдение, но похоже, что компилятор отказывается оптимизировать эту функцию так, как раньше.

Я попытался чтобы удалить метод get_screen из структуры и запустить его как собственную функцию, как раньше, и это, кажется, исправляет его, но только , если я ничего не передаю из структуры. Если я попытаюсь передать даже usize из структуры world в отдельную функцию get_screen, тогда время выполнения увеличится с 3 мс до 14 мс.

Чтобы показать пример того, что я имею в виду, если я ничего не передаю напрямую из структуры world и вместо этого передаю ей клонированную версию 2D-структуры в world и жестко запрограммированную chunk_width / chunk_height:

gen::get_screen(
    &mut screen.buf,
    &cloned_world_data,
    camera_coords,
    SCREEN_WIDTH,
    SCREEN_HEIGHT,
    CHUNK_WIDTH,
    CHUNK_HEIGHT,
);

Он работает в 3.3 РС. Когда я передаю поля usize chunk_width / chunk_height непосредственно из структуры world:

gen::get_screen(
    &mut screen.buf,
    &cloned_world_data,
    camera_coords,
    SCREEN_WIDTH,
    SCREEN_HEIGHT,
    world.chunk_width,
    world.chunk_height,
);

, запускается 14,55 мс

Что здесь происходит? Как я могу заставить мою функцию get_screen скомпилировать встроенную при использовании моей структуры World? Желательно, чтобы я мог повторно добавить его в свою структуру World как метод вместо того, чтобы хранить его отдельно.

Вот минимальный пример:

use std::time::Instant;

const SCREEN_HEIGHT: usize = 1080; //528;
const SCREEN_WIDTH: usize = 1920; //960;
const CHUNK_WIDTH: usize = 256;
const CHUNK_HEIGHT: usize = 256;

const GEN_RANGE: isize = 25; //how far out to gen chunks

fn main() {
    let batch_size = 1_000;
    struct_test(batch_size);
    separate_test(batch_size);
}

fn struct_test(batch_size: u32) {
    let world = World::new(CHUNK_WIDTH, CHUNK_HEIGHT, GEN_RANGE); //generate world
    let mut screen = vec![vec!([0; 4]; SCREEN_WIDTH); SCREEN_HEIGHT];
    let camera_coords: (isize, isize) = (0, 0); //set camera location

    let start = Instant::now();
    for _ in 0..batch_size {
        get_screen(
            &mut screen,
            &world.data,
            camera_coords,
            SCREEN_WIDTH,
            SCREEN_HEIGHT,
            world.chunk_width,
            world.chunk_height,
        ); //gets visible pixels from world as 2d vec
    }
    println!(
        "struct:   {:?} {:?}",
        start.elapsed(),
        start.elapsed() / batch_size
    );
}

fn separate_test(batch_size: u32) {
    let world = World::new(CHUNK_WIDTH, CHUNK_HEIGHT, GEN_RANGE); //generate world
    let cloned_world_data = world.data.clone();
    let mut screen = vec![vec!([0; 4]; SCREEN_WIDTH); SCREEN_HEIGHT];
    let camera_coords: (isize, isize) = (0, 0); //set camera location

    let start = Instant::now();
    for _ in 0..batch_size {
        get_screen(
            &mut screen,
            &cloned_world_data,
            camera_coords,
            SCREEN_WIDTH,
            SCREEN_HEIGHT,
            CHUNK_WIDTH,
            CHUNK_HEIGHT,
        ); //gets visible pixels from world as 2d vec
    }
    println!(
        "separate: {:?} {:?}",
        start.elapsed(),
        start.elapsed() / batch_size
    );
}

///gets all visible pixels on screen relative camera position in world
#[inline(always)] //INLINE STOPPED WORKING??
pub fn get_screen(
    screen: &mut Vec<Vec<[u8; 4]>>,
    world: &Vec<Vec<Chunk>>,
    camera_coords: (isize, isize),
    screen_width: usize,
    screen_height: usize,
    chunk_width: usize,
    chunk_height: usize,
) {
    let camera = get_local_coords(&world, camera_coords, chunk_width, chunk_height); //gets loaded coords of camera in loaded chunks
    (camera.1 - screen_height as isize / 2..camera.1 + screen_height as isize / 2)
        .enumerate()
        .for_each(|(py, y)| {
            //for screen pixel index and particle in range of camera loaded y
            let (cy, ly) = get_local_pair(y, chunk_height); //calculate chunk y and inner y from loaded y
            if let Some(c_row) = world.get(cy) {
                //if chunk row at loaded chunk y exists
                (camera.0 - screen_width as isize / 2..camera.0 + screen_width as isize / 2)
                    .enumerate()
                    .for_each(|(px, x)| {
                        //for screen pixel index and particle in range of camera loaded x
                        let (cx, lx) = get_local_pair(x, chunk_width); //get loaded chunk x and inner x from loaded x
                        if let Some(c) = c_row.get(cx) {
                            screen[py][px] = c.data[ly][lx];
                        }
                        //if chunk in row then copy color of target particle in chunk
                        else {
                            screen[py][px] = [0; 4]
                        } //if target chunk doesn't exist color black
                    })
            } else {
                screen[py].iter_mut().for_each(|px| *px = [0; 4])
            } //if target chunk row doesn't exist color row black
        });
}

///calculates local coordinates in world vec from your global position
///returns negative if above/left of rendered area
pub fn get_local_coords(
    world: &Vec<Vec<Chunk>>,
    coords: (isize, isize),
    chunk_width: usize,
    chunk_height: usize,
) -> (isize, isize) {
    let (wx, wy) = world[0][0].chunk_coords; //gets coords of first chunk in rendered vec
    let lx = coords.0 - (wx * chunk_width as isize); //calculates local x coord based off world coords of first chunk
    let ly = (wy * chunk_height as isize) - coords.1; //calculates local y coord based off world coords of first chunk
    (lx, ly)
}

pub fn get_local_pair(coord: isize, chunk: usize) -> (usize, usize) {
    (coord as usize / chunk, coord as usize % chunk)
}

///contains chunk data
#[derive(Clone)]
pub struct Chunk {
    //world chunk object
    pub chunk_coords: (isize, isize), //chunk coordinates
    pub data: Vec<Vec<[u8; 4]>>,      //chunk Particle data
}

impl Chunk {
    ///generates chunk
    fn new(chunk_coords: (isize, isize), chunk_width: usize, chunk_height: usize) -> Self {
        let data = vec![vec!([0; 4]; chunk_width); chunk_height];
        Self { chunk_coords, data }
    }
}

pub struct World {
    pub data: Vec<Vec<Chunk>>,
    pub chunk_width: usize,
    pub chunk_height: usize,
}

impl World {
    pub fn new(chunk_width: usize, chunk_height: usize, gen_range: isize) -> Self {
        let mut data = Vec::new(); //creates empty vec to hold world
        for (yi, world_chunk_y) in (gen_range * -1..gen_range + 1).rev().enumerate() {
            //for y index, y in gen range counting down
            data.push(Vec::new()); //push new row
            for world_chunk_x in gen_range * -1..gen_range + 1 {
                //for chunk in gen range of row
                data[yi].push(Chunk::new(
                    (world_chunk_x, world_chunk_y),
                    chunk_width,
                    chunk_height,
                )); //gen new chunk and put it there
            }
        }
        Self {
            data,
            chunk_width,
            chunk_height,
        }
    }
}

1 Ответ

2 голосов
/ 07 августа 2020

Вероятно, когда вы используете world.chunk_width и world.chunk_height в качестве параметров, компилятор не рассматривает эти параметры как константы, а затем фактически генерирует операции деления и модуля.

С другой стороны, когда вы предоставляете константы для этих параметров они могут быть распространены в алгоритме (сворачивание констант), а некоторые дорогостоящие операции (деление, модуль) не выполняются (или преобразуются в битовые сдвиги / маски).

Копирование / вставка вашего кода в godbolt (обозреватель компилятора), создавая separate_test() и struct_test() publi c и компилируя с -C opt-level=3, это подтверждает, поскольку инструкции div присутствуют в сгенерированном коде для struct_test(), но не для separate_test().

...