Как получить эффективный максимум с плавающей запятой в Rust

Я тестировал, как получить максимум для массива с плавающей запятой:

pub fn max(n: [f64;8]) -> f64 {
    IntoIterator::into_iter(n).reduce(|a,b| a.max(b)).unwrap()
}

что дает мне (ночная ржавчина)

        vmovsd  xmm0, qword ptr [rdi + 56]
        vmovsd  xmm1, qword ptr [rdi + 48]
        vmovsd  xmm2, qword ptr [rdi + 40]
        vmovsd  xmm3, qword ptr [rdi + 32]
        vmovsd  xmm4, qword ptr [rdi + 24]
        vmovsd  xmm5, qword ptr [rdi + 16]
        vmovsd  xmm6, qword ptr [rdi]
        vmovsd  xmm7, qword ptr [rdi + 8]
        vcmpunordsd     xmm8, xmm6, xmm6
        vmaxsd  xmm6, xmm7, xmm6
        vblendvpd       xmm6, xmm6, xmm7, xmm8
        vcmpunordsd     xmm7, xmm6, xmm6
        vmaxsd  xmm6, xmm5, xmm6
        vblendvpd       xmm5, xmm6, xmm5, xmm7
        vcmpunordsd     xmm6, xmm5, xmm5
        vmaxsd  xmm5, xmm4, xmm5
        vblendvpd       xmm4, xmm5, xmm4, xmm6
        vcmpunordsd     xmm5, xmm4, xmm4
        vmaxsd  xmm4, xmm3, xmm4
        vblendvpd       xmm3, xmm4, xmm3, xmm5
        vcmpunordsd     xmm4, xmm3, xmm3
        vmaxsd  xmm3, xmm2, xmm3
        vblendvpd       xmm2, xmm3, xmm2, xmm4
        vcmpunordsd     xmm3, xmm2, xmm2
        vmaxsd  xmm2, xmm1, xmm2
        vblendvpd       xmm1, xmm2, xmm1, xmm3
        vcmpunordsd     xmm2, xmm1, xmm1
        vmaxsd  xmm1, xmm0, xmm1
        vblendvpd       xmm0, xmm1, xmm0, xmm2
        ret

Поэтому я провожу много времени с обработкой NaN. Я почти уверен, что vmaxsd делает то же самое, что и f64::max в Rust, но не уверен, что я что-то упускаю из виду.

Поэтому я обратился к C++ и получил

double max(double *num) {
    double sum = num[0];
    for (int i = 1; i < 8; i++) {
        sum = std::max(sum, num[i]);
    }
    return sum;
}

который компилируется (в gcc 14.1)

        vmovsd  xmm2, QWORD PTR [rdi]
        vmovsd  xmm1, QWORD PTR [rdi+8]
        vmaxsd  xmm0, xmm1, xmm2
        vmovsd  xmm1, QWORD PTR [rdi+16]
        vmovsd  xmm2, QWORD PTR [rdi+24]
        vmaxsd  xmm1, xmm1, xmm0
        vmaxsd  xmm0, xmm2, xmm1
        vmovsd  xmm2, QWORD PTR [rdi+32]
        vmaxsd  xmm1, xmm2, xmm0
        vmovsd  xmm2, QWORD PTR [rdi+40]
        vmaxsd  xmm0, xmm2, xmm1
        vmovsd  xmm2, QWORD PTR [rdi+48]
        vmaxsd  xmm1, xmm2, xmm0
        vmovsd  xmm0, QWORD PTR [rdi+56]
        vmaxsd  xmm0, xmm0, xmm1
        ret

(нет опции быстрой математики, просто -O3)

что заставляет меня думать, что сборка из Rust неоптимальна или семантика C++ max и Rust max различна.

Может ли кто-нибудь пролить свет на этот вопрос? И как я могу написать здесь тот же код, что и C++, с помощью Rust?

За пределами сигналов Angular: Сигналы и пользовательские стратегии рендеринга
За пределами сигналов Angular: Сигналы и пользовательские стратегии рендеринга
TL;DR: Angular Signals может облегчить отслеживание всех выражений в представлении (Component или EmbeddedView) и планирование пользовательских...
Sniper-CSS, избегайте неиспользуемых стилей
Sniper-CSS, избегайте неиспользуемых стилей
Это краткое руководство, в котором я хочу поделиться тем, как я перешел от 212 кБ CSS к 32,1 кБ (сокращение кода на 84,91%), по-прежнему используя...
8
0
201
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Документация f64::max сообщает нам:

Если один из аргументов равен NaN, то возвращается другой аргумент.

Таким образом, он выдает NaN только тогда, когда оба аргумента равны NaN.

Но std::max использует < для сравнения, которое может дать NaN, если только один из операндов равен NaN. Аналогично MAXSD всегда возвращает второй операнд, если любой из них равен NaN, и, таким образом, также может возвращать NaN только с одним (вторым) операндом, равным NaN:

MAX(SRC1, SRC2)
{
    IF ((SRC1 = 0.0) and (SRC2 = 0.0)) THEN DEST := SRC2;
        ELSE IF (SRC1 = NaN) THEN DEST := SRC2; FI;
        ELSE IF (SRC2 = NaN) THEN DEST := SRC2; FI;
        ELSE IF (SRC1 > SRC2) THEN DEST := SRC1;
        ELSE DEST := SRC2;
    FI;
}

Таким образом, хотя MAXSD и C++ std::max имеют совместимую семантику, Rust f64::max несовместим:

std::cout << std::max(nan, 1.0) << " " << std::max(1.0, nan); // → nan 1
println!("{} {}", f64::max(nan, 1.0), f64::max(1.0, nan));    // → 1 1

Использование той же семантики в Rust приводит к эквивалентной сборке:

pub fn max(n: [f64;8]) -> f64 {
    n.into_iter().reduce(|a,b| if a < b { b } else { a }).unwrap()
}
example::max::h17b765fea01ee3b1:
        movsd   xmm0, qword ptr [rdi + 56]
        movsd   xmm1, qword ptr [rdi + 48]
        movsd   xmm2, qword ptr [rdi + 40]
        movsd   xmm3, qword ptr [rdi + 32]
        movsd   xmm4, qword ptr [rdi + 24]
        movsd   xmm5, qword ptr [rdi + 8]
        maxsd   xmm5, qword ptr [rdi]
        movsd   xmm6, qword ptr [rdi + 16]
        maxsd   xmm6, xmm5
        maxsd   xmm4, xmm6
        maxsd   xmm3, xmm4
        maxsd   xmm2, xmm3
        maxsd   xmm1, xmm2
        maxsd   xmm0, xmm1
        ret

Можете ли вы объяснить, в чем смысловая разница? if a < b { b } else { a }, похоже, дает те же результаты в отношении NaN, что и f64::max

Unlikus 08.05.2024 19:39

Оно отличается, когда a равно NaN, где this if возвращает NaN, а f64::max возвращает b

cafce25 08.05.2024 19:51

Другие вопросы по теме