LINUX.ORG.RU

параллельность в расте

 ,


1

9

Здравствуйте. Я тут немного решил поэксперементировать с параллельностью в расте и сделал маленький генератор фракталов. И был немного удивлен, когда увидел, то при распараллеливаниии на несколько тредов, производительность не только лишь не увеличивается, но еще и уменьшается! Это я криворукий или треды в расте нифига не параллельны?

extern crate image;

use std::thread;
use std::sync::{Mutex, Arc};
use std::time;
use image::*;
use std::env;

fn calculate_point(x : f64, y : f64, depth : usize) -> f32 {
    let mut zx = 0.0;
    let mut zy = 0.0;
    let mut zx1;
    let mut zy1;
    for i in 0 .. depth {
        zx1 = (zx * zx) - (zy * zy) + x;
        zy1 = zx * zy * 2.0 + y;
        if zx1 * zx1 + zy1 * zy1 > 4.0 {
            return (i as f32) / (depth as f32);
        }
        else {
            zx = zx1;
            zy = zy1;
        }
    }
    return 1.0;//depth as f32;
}

fn get_color(colors : &Vec<[f32;3]>, buffer : &mut Vec<[f32;3]>, x : f32) -> Rgb<u8> {
    let mut len = colors.len() - 1;
    for i in 0 .. len {
        for j in 0 .. 3 {
            buffer[i][j] = colors[i][j] + (colors[i+1][j] - colors[i][j]) * x
        }
    }
    while len > 1 {
        for i in 0 .. len - 1 {
            for j in 0 .. 3 {
                buffer[i][j] = buffer[i][j] + (buffer[i+1][j] - buffer[i][j]) * x
            }
        }
        len -= 1;
    }
    return Rgb([buffer[0][0] as u8, buffer[0][1] as u8, buffer[0][2] as u8]);
}

fn make_pic_1th(width : u32, height : u32) -> RgbImage {
    let mut pic = ImageBuffer::new(width, height);
    let colors : Vec<[f32;3]> = vec![[0.0,0.0,0.0], [255.0,0.0,0.0], [255.0,255.0,0.0], [255.0,255.0,255.0]];
    let mut buffer = colors.clone();
    for i in 0 .. width {
        let x = (i as f64) / (width as f64) * 2.0 - 1.0;
        for j in 0 .. height {
            let y = (j as f64) / (height as f64) * 2.0 - 1.0;
            (*pic.get_pixel_mut(i,j)) = get_color(&colors, &mut buffer, calculate_point(x, y, 1000));
        }
    }
    pic
}

unsafe fn calc_rows(index : u32, step : u32, width : u32, height : u32, pic : SendPtr<RgbImage>, live_flag : Arc<Mutex<bool>>) {
    let colors : Vec<[f32;3]> = vec![[0.0,0.0,0.0], [255.0,0.0,0.0], [255.0,255.0,0.0], [255.0,255.0,255.0]];
    let mut buffer = colors.clone();
    let pic       : *mut RgbImage = match pic {SendPtr(a) => a};
    for i in 0 .. width {
        let x = (i as f64) / (width as f64) * 2.0 - 1.0;
        let mut j = index;
        while j < height {
            let y = (j as f64) / (height as f64) * 2.0 - 1.0;
            (*(*pic).get_pixel_mut(i,j)) = get_color(&colors, &mut buffer, calculate_point(x, y, 1000));
            j += step;
        }
    }
    (*live_flag.lock().unwrap()) = false;
}

struct SendPtr<A>(*mut A);
unsafe impl<A> Send for SendPtr<A> {}
unsafe impl<A> Sync for SendPtr<A> {}

fn make_pic_3th(width : u32, height : u32) -> RgbImage {
    let mut pic = ImageBuffer::new(width, height);
    let f1 = Arc::new(Mutex::new(true));
    let f2 = Arc::new(Mutex::new(true));
    let f3 = Arc::new(Mutex::new(true));
    unsafe {
        macro_rules! spawn {($i:expr,$m:expr,$flag:expr) => {
            let ppic  = SendPtr(&mut pic);
            let pflag = $flag.clone();
            thread::spawn(move|| {calc_rows($i, $m, width, height, ppic, pflag)});
        }};
        spawn!(0, 3, f1);
        spawn!(1, 3, f2);
        spawn!(2, 3, f3);
    }
    let dur = time::Duration::from_millis(50);
    loop {
        let a : bool = *f1.lock().unwrap();
        let b : bool = *f2.lock().unwrap();
        let c : bool = *f2.lock().unwrap();
        if a || b || c {
            thread::sleep(dur)
        } else {
            break
        }
    }
    pic
}

fn main() {
    let args : Vec<String> = env::args().collect();
    let pic = if args[1] == "1" {make_pic_1th(1000,1000)} else {make_pic_3th(1000, 1000)};
    match pic.save("out.png") {
        Ok(_) => (),
        Err(e) => {
            println!("error: {:?}", e)
        }
    }
}

★★★★★

Господи, зачем там ансейф? О_о

Треды в расте точно такие же треды как и везде. Ищи косяк у себя. Судя по куче блокировок, твои потоки вполне могут исполняться не параллельно.
Ну и задержки в 50мс в числодробильном коде это не самая лучшая идея. Задержек вообще быть не должно.

mersinvald ★★★★★
()
Последнее исправление: mersinvald (всего исправлений: 1)

Раст не взлетит потому что не позволяет писать говнокод

Раст не взлетит потому что не позволяет писать говнокод. Говнокод нужен для технического долга, который так сильно нужен бизнесу, что тот аж пердит и подпрыгивает, теряя деньги, платя за разработку якобы не текущих программ на расте-хуясте годами. А поскольку основной целью бизнеса - деньги, то и раст будет послан бизнесом в опу. Нужен не раст, а язык, позволяющий брать технический долг, причём бесплатно. Именно такой язык выстрелит и убьёт спп. А раст - гуано крокодила, смешанное с мочой антилопы гну. Брось каку.

anonymous
()

Треды в Расте определенно параллельны. Но, судя по обилию unsafe, ты определенно делаешь что-то не так. И насколько я понимаю, Ъ-способ параллелить в Rust - не мютексы, а каналы: http://rustbyexample.com/std_misc/channels.html

tailgunner ★★★★★
()
Последнее исправление: tailgunner (всего исправлений: 1)
Ответ на: комментарий от mersinvald

В мьютекс тред лезет только по завершению вычисления, так что блокировок, которые влияют на тред у меня всего три за все время исполнения. Ансейф использую именно для того, что б уменьшить затраты на синхронизацию. При уменьшении задержки в проверке завершения вычислений, скорость не меняется.

Aswed ★★★★★
() автор топика

Раст не взлетит потому что не позволяет писать говнокод

Ты сам себе противоречишь. Если то что в топике — не говнокод, значит в расте действительно криво реализован параллелизм. Ты уж определись тогда, либо то, либо это:)

asteroidcollide
()
Ответ на: комментарий от Aswed

Ансейф использую именно для того, что б уменьшить затраты на синхронизацию

Сначала реализуй грамотно ожидание завершения и синхронизацию средствами раста, а потом пытайся «оптимизировать» своими ансейф-подпорками. Ставлю 100 к одному что это не потребуется или не возымеет никакого эффекта.

mersinvald ★★★★★
()
Ответ на: комментарий от asteroidcollide

Аноним похоже пытался донести, что на расте не получится генерировать рабочий код прыгая жопой по клавиатуре. Что код в посте как бы и подтверждает

mersinvald ★★★★★
()
Ответ на: комментарий от mersinvald

Но, насколько я понимаю, код в посте, таки рабочий, просто он не эффективный. То есть, он как раз и подпадает под определение говнокода, что противоречит утверждению анонима.

asteroidcollide
()
Ответ на: комментарий от asteroidcollide

Таки рабочий. А как именно сделать эффективнее? У меня есть много пикселей. Их надо вычислить. На значение одного пиксела не влияют значения других. Пускаем в разных тредах вычислять значения разных строк. Изображение передается в виде обычного указателя, блокировок при записи в пикселы нет. Казалось бы, все должно работать быстрее, ан вот нет.

Aswed ★★★★★
() автор топика
Ответ на: комментарий от Aswed

Да, я сам уже тут поковыриваю этот код, вижу что не повлияло) Кстати, не знаю как ты мерил, но у меня при выполнении в 3 потока в 2 раза ускорение.

mersinvald ★★★★★
()
Ответ на: комментарий от mersinvald

Возможно, что у меня тут на работе мак не хочет параллелить по-нормальному. Попробую дома на линуске запустить.

Aswed ★★★★★
() автор топика
Ответ на: комментарий от mersinvald

вот собсна мои результаты

⧖ 2:46:17 λ> time ../target/release/julia 3                                                                                                                                           ~/code/tests.rust/julia/src
../target/release/julia 3  2.02s user 0.01s system 266% cpu 0.762 total
⧖ 2:46:23 λ> time ../target/release/julia 1                                                                                                                                           ~/code/tests.rust/julia/src
../target/release/julia 1  1.86s user 0.01s system 99% cpu 1.881 total

Aswed ★★★★★
() автор топика

Опытный программист на це сможет на любом языке написать программу на це.

anonymous
()
Ответ на: комментарий от Aswed

По моему у вас все нормально работает.
Не рассматриваем unsafe и тд.

[someuser@somehost julia]$ time target/release/julia 1

real    0m1.769s
user    0m1.767s
sys     0m0.000s
[someuser@somehost julia]$ time target/release/julia 3

real    0m0.470s
user    0m1.217s
sys     0m0.007s

uglym8
()
Ответ на: комментарий от asteroidcollide

Анонимус забыл звездочку добавить что в коде не должно быть слова unsafe. Этим магическим словом можно превратить любой код в говнокод.

pftBest ★★★★
()
Последнее исправление: pftBest (всего исправлений: 1)

А сколько процессоров в /proc/cpuinfo? А то мало ли.

O02eg ★★★★★
()
Ответ на: комментарий от Aswed

На локальность обращений влиять, конечно. можно. Если по-простому, не следует из разных нитей обращаться к одному участку памяти, когда хотя бы одно обращение - запись. Если не так просто. то вот: https://www.akkadia.org/drepper/cpumemory.pdf.

Но похоже, что у тебя что-то другое (помимо того, что программа в сущности на Си:)).

tailgunner ★★★★★
()
Ответ на: комментарий от Aswed

так у тебя нет никакой проблемы, я проверил у себя на машине и с параметром 3 работает в три раза быстрее чем с параметром 1

pftBest ★★★★
()
Ответ на: комментарий от pftBest

параметром 3 работает в три раза быстрее чем с параметром 1

Больше чем в 3 раза, что тоже подозрительно.

tailgunner ★★★★★
()

Вот, накидал на коленке реализацию без unsafe

fn make_pic_1000th(width: u32, height: u32) -> RgbImage {
    let mut pic: RgbImage = ImageBuffer::new(width, height);
    let colors: Vec<[f32; 3]> =
        vec![[0.0, 0.0, 0.0], [255.0, 0.0, 0.0], [255.0, 255.0, 0.0], [255.0, 255.0, 255.0]];
    let chunk_size = pic.len() / height as usize;

    pic.par_chunks_mut(chunk_size)
        .enumerate()
        .weight_max()
        .for_each(|(j, v)| {
            let mut row: ImageBuffer<Rgb<u8>, &mut [u8]> = ImageBuffer::from_raw(width, 1, v)
                .unwrap();
            let mut buffer = colors.clone();
            let y = (j as f64) / (height as f64) * 2.0 - 1.0;
            for i in 0..width {
                let x = (i as f64) / (width as f64) * 2.0 - 1.0;
                (*row.get_pixel_mut(i, 0)) =
                    get_color(&colors, &mut buffer, calculate_point(x, y, 1000));
            }
        });
    pic
}

pftBest ★★★★
()
Ответ на: комментарий от tailgunner

Т.е. в подобной ситуации правильнее сделать вычисления в отдельных участках памяти, а потом сделать fold в одном потоке?

Aswed ★★★★★
() автор топика
Ответ на: комментарий от Aswed

То есть нужно избегать конфликтов по памяти. Разделить работу по нитям и потом объединить ее - самый общий подход, но ножно и аккуратно разносить данные по строкам кэша.

А еще важно не месить память в стиле Си, а явно выделять структуры данных - например, как сделал red75prim.

tailgunner ★★★★★
()
Последнее исправление: tailgunner (всего исправлений: 2)

Этот код нужно отрефакторить просто для того, чтобы его было возможно прочесть. Даже самая первая функция это какое-то месиво вместо простого и очевидного

fn calculate_point(x: f64, y: f64, depth: usize) -> f32 {
    let (mut zx, mut zy) = (0.0, 0.0);

    for i in 0..depth {
        zx = zx*zx - zy*zy + x;
        zy = 2.0 * zx*zy + y;

        if zx*zx + zy*zy > 4.0 {
            return (i as f32) / (depth as f32);
        }
    }
    
    1.0
}

quantum-troll ★★★★★
()
Ответ на: комментарий от Aswed

Может ты попутал user time и total time? Я вот смотрю на твои результаты с мака и вижу что 1 тред отработал за 1.881 секунды а три треда справились за 0.762 секунды, что в 2.57 раз быстрее.

pftBest ★★★★
()
Ответ на: комментарий от quantum-troll

Молодец, отлично отрефакторил. А теперь сравни результаты работы.

    zx = zx*zx - zy*zy + x;
    zy = 2.0 * zx*zy + y;

Aswed ★★★★★
() автор топика
Ответ на: комментарий от Aswed

Ок, понял.

fn calculate_point(x: f64, y: f64, depth: usize) -> f32 {
    let (mut zx, mut zy) = (0.0, 0.0);

    for i in 0..depth {
        let (zx1, zy1) = (
            zx*zx - zy*zy + x,
            2.0 * zx*zy + y,
        );

        if zx1*zx1 + zy1*zy1 > 4.0 {
            return (i as f32) / (depth as f32);
        }

        zx = zx1;
        zy = zy1;
    }
    
    1.0
}

quantum-troll ★★★★★
()

Ожидание завершения потоков можно переписать так:

fn make_pic_3th(width : u32, height : u32) -> RgbImage {
    let mut pic = ImageBuffer::new(width, height);
    let max_step = 3;
    let mut threads: Vec<_> = 
        (0..max_step).map(|i|{
            let ppic = SendPtr(&mut pic);
            thread::spawn(move|| {calc_rows(i, max_step, width, height, ppic)})
        }).collect();
    for t in threads.drain(..) {
        t.join().unwrap();
    }
    pic
}

red75prim ★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.