LINUX.ORG.RU

Правильно ли я пишу на Rust?

 , ,


1

7

Часто мелькает Rust и в новостях, и в темах. Решил попробовать переписать один тест с С на Rust. Для сравнения написал вариант и на C++. На Rust получилось в 4+ раза медленнее, чем на С и в 2+ раза медленнее, чем на C++. Есть подозрение, что я делаю что-то неправильно, но не знаю что. Помогите, пожалуйста, разобраться.

UPD. Мои цифры:

$ gcc c_v1.c -Ofast -march=native
$ ./a.out 3000
16.439091
-287.250083
$ g++ cpp_v2.cpp -Ofast -march=native
$ ./a.out 3000
31.3826
-287.25
$ rustc rust_v1.rs -C opt-level=3 -C target-cpu=native
$ ./rust_v1 3000
71.570172703s
-287.2500833333321
★★★

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

Ответ на: комментарий от RazrFalcon

Нету смысла цепляться за fast_math. Он тут полностью до лампочки.

Мы уже определили что at() с ювелирной точностью замедляет C++ до скорости Rust.

Вопрос остается, почему unsafe+get_unchecked+get_unchecked_mut обратно не ускоряет Rust до скорости С++ с квадрантыми скобками.

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

А то ieee формат не вносит. Всё равно если важна точность нужно менять алгоритм вычислений и приводить на каждом шаге округление к нужной точности.

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

В плюсовом тесте измеряется не производительность языка, а производительность и размер кеша.

Ну вот, кстати, вариант cpp_v2.cpp от ТС-а показывает у меня такую же скорость, как евоный c_v1.c, но без restrict-а.

Так что дело в restrict-е.

PS. Мерял посредством gcc-8.

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

Так-с, код на C++ уже замедлили, осталось «всего лишь» ускорить код на Rust.

anonymous
()

На Debian testing не компилируются:

$ gcc -Wall -g -O2 -march=native -o c_v2 c_v2.c 
c_v2.c: In function ‘matrix_alloc’:
c_v2.c:55:25: error: too few arguments to function ‘calloc’
   result[i] = (double *)calloc(N * sizeof(double));
                         ^~~~~~
In file included from c_v2.c:2:
/usr/include/stdlib.h:541:14: note: declared here
 extern void *calloc (size_t __nmemb, size_t __size)
              ^~~~~~
$ gcc --version
gcc (Debian 8.3.0-6) 8.3.0
$ rustc -C opt-level=2 -C target-cpu=native rust_v1.rs 
error[E0554]: #![feature] may not be used on the stable release channel
 --> rust_v1.rs:1:1
  |
1 | #![feature(core_intrinsics)] //nightly needed
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0554`.
$ rustc --version
rustc 1.35.0

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

С вариантом отсюда: Правильно ли я пишу на Rust? (комментарий)

при сравнении с сишной версией у меня получается так:

red75:/mnt/d/git/test9$ target/release/test9 3000
13.5560623s
-287.2500833333321
red75:/mnt/d/git/test9$ ./a.out 3000
13.578125
-287.250083
red75:/mnt/d/git/test9$ ./a.out 3000
13.343750
-287.250083
red75:/mnt/d/git/test9$ target/release/test9 3000
14.6958572s
-287.2500833333321
red75:/mnt/d/git/test9$ target/release/test9 3000
13.3552161s
-287.2500833333321
red75:/mnt/d/git/test9$ ./a.out 3000
13.437500
-287.250083
red75:/mnt/d/git/test9$ target/release/test9 3000
13.5398263s
-287.2500833333321
red75:/mnt/d/git/test9$
red75prim ★★★
()
Ответ на: комментарий от pon4ik

Я про вектор который копируется как минимум.

Там размеры вектора заданы явно, что равносильно resize.

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

Да. Те же самые ключики компиляции, тот же самый аргумент при запуске:

~/sandboxes/tmp/lor_mat_mul_speed$ ./c_v1_norestrict 3000
28.244578
-287.250083
~/sandboxes/tmp/lor_mat_mul_speed$ ./c_v1_norestrict 3000
28.048929
-287.250083
~/sandboxes/tmp/lor_mat_mul_speed$ ./cpp_v2 3000
27.5893
-287.25
~/sandboxes/tmp/lor_mat_mul_speed$ ./cpp_v2 3000
27.6256
-287.25
eao197 ★★★★★
()
Ответ на: комментарий от red75prim

Ну там функциональный код, он может тоже не делать проверки кстати, так как там функции заранее известно что она движется по валидным индексам. А ФП оптимизатор вытрет. Так что мне кажется это из той же оперы, но что-то заставило оптимизатор таки сработать лучше с функциональным кодом чем get_unchecked

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

А если ещё и транспонирование на вариант с chunks_exact поменять, то начинает немного обгонять C. Нет, не начинает. Ошибка с индексами.

    for (i, b_row) in b.chunks_exact(n).enumerate() {
        for (j, &b_val) in b_row.iter().enumerate() {
            unsafe {
                *t.get_unchecked_mut(j*n + i) = b_val;
            }
        }
    }
red75prim ★★★
()
Последнее исправление: red75prim (всего исправлений: 2)
Ответ на: комментарий от RazrFalcon

Не, ну я про нарушения стандарта ieee не знал, но обоснование насчёт точности канает только в двух случаях, и ни один из них не подходит под этот конкретный код.

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

Тогда остаётся gcc vs llvm.

Я тут давеча Skia тестировал. Так вот версия собранная gcc в три раза медленнее. И это на одной кодовой базе. А мы про разные языки говорим...

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

Там рядом был было два примера практически идентичного кода на С и С++. Сейчас копировал ссылки и заметил пару дурацких ошибок, но на результате это не сказалось. Разница только в malloc/new и в restrict.

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

Завтра ТС забудет написать f*_fast и опять будет рассказывать про «медленный» Rust.

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

Да облажался при смене malloc на calloc. Заметил пару минут назад, когда копировал ссылку для ответа другому участнику, уже поправил.

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

gcc vs llvm

Какой-то туповатый аргумент. Rust же на LLVM, и он медленнее.

А сишный вариант собирается gcc, и почему-то «медленным» gcc он получает двойное преимущество перед языками для школьников вроде Rust.

anonymous
()

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

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от red75prim

Да, действительно. Сейчас под рукой другая машина, потому цифры будут другие:

$ clang -Ofast -march=native c_v1.c -o c
$ ./c 3000
17.679873
-287.250083
$ clang++ -Ofast -march=native cpp_v1.cpp -o cpp
$ ./cpp 3000
55.875
-287.25
$ rustc rust_v1.rs -C opt-level=3 -C target-cpu=native
$ ./rust_v1 3000
17.34767689s
-287.2500833333321

В целом код понятен (не смотря на то, что я совсем не знаю Rust). За исключением этой строки:

*c_val = a_row.iter().zip(t_row.iter()).fold(0.0, |sum, (&a,&t)| unsafe{ fadd_fast(fmul_fast(a,t), sum) } );

Видимо, тут есть лямбда, но кроме неё незнакомые для меня zip, fold итд. Наверное, они относятся к приёмам функционального программирования, об этом есть в книге о Rust? Или лучше сначала где-то в другом месте почитать об этом и потом браться за книгу о Rust?

andalevor ★★★
() автор топика
Ответ на: комментарий от LINUX-ORG-RU

берётся матёрый сишник, растоман и плюсовик и переписывают всё это исключительно в рамках языков

И получается что все пишут на C, «без фич». Но такие языки как руст и цепп задумывались как зеро-кост абстракшнз. Т.е. писать на них должно быть легче и удобнее и безопаснее, а скорость должна быть как на С. Чёт не выходит.

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

Мне пихают во все дыры.

Владимир

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

Про итераторы и замыкания немного есть тут: https://doc.rust-lang.org/book/ch13-00-functional-features.html

Что почитать по функциональным штукам (zip, map, fold и т.п.) не подскажу. Применительно к Расту, всё что они делают неплохо описано в https://doc.rust-lang.org/core/iter/trait.Iterator.html

zip объединяет пару итераторов в один, который выдаёт пары значений.

iter.fold(init, f) - это, в общем, цикл по итератору. Делает то же самое, что и

let mut state = init;
for v in iter {
    state = f(state, v);
};
return state;
red75prim ★★★
()
Ответ на: комментарий от gag

А что с rust? Он в Debian уже 4 с половиной года.

Не совсем понимаю ваш вопрос. Как связана тема топика, с дебианом?

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

Тяжело судить, быстрее код или нет, если он даже не собирается. Зато напрашивается очевидный вывод, лучше такой код или хуже.

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

calloc вы быстро поправили и к нему больше не возвращались. Я о rust. В gcc тоже есть фичи, как подметили выше: fast-math. Они доступны на выбор каждого. При использовании rust для софта, которое будет реально использоваться, необходимо обходиться без этих nightly. И поэтому вопрос остаётся открытым.

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

Во всём дистрибутиве Debian весь софт, использующий rust, компилируется rustc из самой же Debian. А, значит, и во всех дочерних дистрибутивах. Интересно, как там с этим в Fedora, SUSE,...

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

Это не мой (маргинальный) дистр, это Debian. Да, это не проблемы языка, а стандартного компилятора.

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

Отойдёте от Владимира, придёте к Арсению.

Георгий

anonymous
()

Часто мелькает Rust и в новостях, и в темах. Решил попробовать переписать один тест с С на Rust.

Советую переписать на Python. Если уж смысл в том, чтобы писать тесты.

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

Код забыл:

{-# LANGUAGE LambdaCase #-}

module Main (main) where

import System.Clock (Clock(ProcessCPUTime), getTime, toNanoSecs)
import System.Environment (getArgs)
import System.Exit (exitFailure)
import Text.Printf (printf)

type Matrix = [[Double]]

main :: IO ()
main = do
  n <- getArgs >>= \case
    [a] -> pure (read a)
    _ -> exitFailure

  let a = newMatrix n
      b = newMatrix n
      c = newMatrix n

  t1 <- clock
  let c' = matrixMult n a b c
  t2 <- clock

  printf "%dns\n" (t2 - t1)
  printf "% 8.6f\n" (c' !! (n `div` 2) !! (n `div` 2))

newMatrix :: Int -> Matrix
newMatrix n =
  let tmp = 1 / fromIntegral n / fromIntegral n in
  [ [ tmp * (i - j) * (i + j)
    | j <- [0 .. fromIntegral (pred n)]
    ]
  | i <- [0 .. fromIntegral (pred n)]
  ]

matrixMult :: Int -> Matrix -> Matrix -> Matrix -> Matrix
matrixMult n a b _ =
  let t = [ [ b!!j!!i
            | j <- [0 .. pred n]
            ]
          | i <- [0 .. pred n]
          ]
      c = [ [ sum [ a!!i!!k * t!!j!!k | k <- [0 .. pred n] ]
            | j <- [0 .. pred n]
            ]
          | i <- [0 .. pred n]
          ]
  in c

matrixPrint :: Int -> Matrix -> String
matrixPrint _ = unlines . fmap (unwords . fmap (printf "% 8.6f"))

clock :: IO Integer
clock = toNanoSecs <$> getTime ProcessCPUTime
Laz ★★★★★
()
Ответ на: комментарий от Laz

Если в конец main добавить

  t3 <- clock
  printf "%dns\n" (t3 - t2)

То получим:

$ ghc -O test9.hs
[1 of 1] Compiling Main             ( test9.hs, test9.o )
Linking test9 ...
$ ./test9 3000
0ns
-287.250083
921875000ns
red75prim ★★★
()

а в чем вообще смысл этого бенчмарка? Быстрое умножение матриц - очень сложное дело, для которого как правило применяют библиотеки. Для раста например есть https://crates.io/crates/matrixmultiply

anonymous
()

На, я поменял твою С++версию на нормальную:

#include <iostream>
#include <vector>
#include <ctime>
#include <valarray>
#include <cstdlib>

using matrix_t = std::vector<std::valarray<double>>;
auto matrix = [](size_t n) { return matrix_t{n, std::valarray<double>(n)}; };


void fill(matrix_t & m) {
  auto n = m.size();
  auto tmp = 1. / n / n;
  for(ssize_t i = 0; i < ssize_t(n); ++i)
    for(ssize_t j = 0; j < ssize_t(n); ++j)
      m[i][j] = tmp * (i - j) * (i + j);
}

[[gnu::always_inline]] inline void mult(const matrix_t & a, const matrix_t & b, matrix_t & c) {
  auto n = a.size();
  auto tmp = matrix(n);
  for(size_t i = 0; i < n; ++i) {
    for(size_t j = 0; j < n; ++j)
      tmp[i][j] = b[j][i];
  }
  
  for(size_t i = 0; i < n; ++i) {
    for(size_t j = 0; j < n; ++j)
      c[i][j] = (a[i] * tmp[j]).sum();
  }
}


int main(int argc, char * argv[]) {
  size_t N = 3000;
  if(argc > 1) N = std::stol(argv[1]);

  std::cerr << N << std::endl;

  auto a = matrix(N), b = matrix(N), c = matrix(N);

  fill(a);
  fill(b);
  
  clock_t t1 = clock();
  mult(a, b, c);
  clock_t t2 = clock();
  
  std::cerr << (double)(t2 - t1) / CLOCKS_PER_SEC << "\n";
  std::cerr << c[N/2][N/2] << "\n";
}

Собирать так:

clang++ main.cpp -march=native -Ofast -std=gnu++2a  -stdlib=libc++ -funroll-loops 

Так же можно попробовать добавить -mno-fma

Это самая быстрая версия. Хотя в любом случае это дерьмо достойное только всяких недоязычков и их адептов.

Как написать нормальный dot product я уже писал на лоре.

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