На Хабре уже было тестирование Эльбрусов на разных языках программирования (например, здесь). И данный обзор стоит рассматривать как дополнение, с ещё одним тестом, новыми версиями компиляторов и новыми участниками (Rust, С++). Также обзор сделан с упором на тест возможностей именно компиляторов и настройки оптимизации.

Тестовые стенды:

x86:

  • AMD FX-6300@3500 Мгц. (турбобуст отключен).

  • Intel Celeron (Haswell) G1820@2700 Мгц.

Софт:

Ubuntu 22.04.

Java: OpenJDK Runtime Environment (build 11.0.25+9-post-Ubuntu-1ubuntu122.04).

Rust: rustc / cargo v.1.83.0; LLVM version: 19.1.1.

C++: GCC v11.4.0; LLVM version 19.1.5.

e2k:

  • Эльбрус 8С@1200 Мгц.

Софт:

Java: OpenJDK Runtime Environment (build 11.0.15-Unipro+0-adhoc.root.openjdk11-11.0.15).

Rust: rustc / cargo v.1.57.0.

C++: GCC v9.3.0 compatible; LLVM version 13.0.1.

Испытуемые: Java, Rust, C++(GCC, LLC).

Тест

В качестве теста выступает решето Эратосфена в блочном варианте. Один поток. На трёх языках реализовано максимально идентично. Программа в консольном варианте. Есть возможность повторного расчёта.

  • исходник Java;

  • исходник Rust;

  • исходник C++.

Методика тестирования

Выполняем два запуска по пять прогонов поиска простых чисел в диапазоне 0 - 5*108. Первый прогон разогревочный и в расчёт не идёт. Для Java прогревочный проход обязателен. И, как показала практика, на C++ и Rust первый прогон тоже чуть медленнее. По оставшимся результатам двух прогонов вычисляется средний показатель.

Результаты тестов

В сравнении gcc и llc, gcc оказался чуть эффективнее, но разница очень маленькая, меньше 1%. Поэтому ниже в тестах будут фигурировать значения llc с обозначением gcc/llc. Значения указаны в миллисекундах.

Некоторые неожиданности.

Ожидалось, что исполняемые файлы, полученные на одной x86-машине, должны работать на другой x86-машине. Но C++ программа, скомпилированная на AMD FX с настройкой оптимизации выше, чем O0, на Intel Celeron (Haswell) выпадала с ошибкой: "Недопустимая инструкция (образ памяти сброшен на диск)". Однако с Rust таких проблем не наблюдалось. И так же всё, что скомпилировано на Intel, работало на AMD FX. При этом, независимо от того, на какой машине был получен исполняемый файл, результаты тестов совпадали.

gcc
O0

gcc/llc O2

gcc/llc O3

gcc/llc O4

Rust
O2

Rust
O3

Java

Elbrus 8C @ 1.2Ghz

45712

4694

3912

3830

4941

5033

19357

AMD FX @ 3.5Ghz

7743

2373

2208

2205

1818

1918

4635

Cel G1820 @ 2.7Ghz

7508

1648

1523

1543

1183

1213

5123

Жирным выделены результаты самых удачных настроек компиляции для каждой машины.

Сразу бросается в глаза разница между gcc O0 и gcc/llc O2 на Эльбрусе. Такова плата за запуск неоптимизированных программ на e2k.

В отличие от x86, для Эльбруса оптимизация O4 даёт профит.

А вот для Rust везде лучший результат показала оптимизация O2.

Итак, лучшие настройки оптимизации для данного теста на платформах:

  • x86: Rust - O2, C++ - O3.

  • e2k: Rust - O2, C++ - O4.

P.S.

Не являюсь специалистом по Rust и C++. Код достаточно простой и там вряд ли будут серьёзные недочёты. Но мог недоработать в плане настроек компиляции.
Благодарю компанию МЦСТ за возможность ознакомления с ЦП семейства Эльбрус!

Комментарии (30)


  1. m0xf
    13.12.2024 15:50

    Какой же медленный этот эльбрус. Celeron G1820 - процессор 2014 года с весьма посредственной производительностью. Если сравнить с современным процессором разница производительности будет уже в десятки раз.


    1. ss-nopol
      13.12.2024 15:50

      Для 99.9% процентов офисных задач достаточно.


      1. VVS_AMD Автор
        13.12.2024 15:50

        8С вряд ли пропишется в каком-нибудь офисе. Это скорее ЦП для серверов. 2С3 для офисов больше подходит. У него и однопоточая производительность значительно выше, раза в два - три.


        1. Armmaster
          13.12.2024 15:50

          На данной задаче разница между 8С и 2С3 должна скалироваться согласно частоте, т.е. улучшение будет 2/1.2 = 1.67 раза.


          1. VVS_AMD Автор
            13.12.2024 15:50

            Я, как бы, имел ввиду в общем...
            Ну а в этой задаче, почему линейно частоте? У 2C3 пиковая производительность в два раза больше операций за такт. Вы смотрели исходники теста? По моему, самая затратная функция makeHoles() отлично разложится в широкую команду любой длины. Там блоки по 10000!


            1. Armmaster
              13.12.2024 15:50

              Ох уж эти операции за такт, было бы дело в них, Эльбрусу не было бы равных!)

              В 2 раза больше операций в такт это за счёт векторизации, причем для плавающих инструкций в первую очередь. Здесь это вряд ли актуально.


              1. VVS_AMD Автор
                13.12.2024 15:50

                Жаль, что нет образца потестить. Самое крутое, что есть в общем доступе, это E8C.


                1. Armmaster
                  13.12.2024 15:50

                  Развлекайтесь:

                  https://ce.mentality.rip/


              1. OpenA
                13.12.2024 15:50

                Ох уж эти операции за такт, было бы дело в них, Эльбрусу не было бы равных!)

                на каждую такую операцию в интеле/амд найдется дополнительная стадия конвеера и преимуществ в итоге не видно.

                Разница лишь в том что эльбрусу такая производительность дается относительно легко и дешево, тогда как над длинным конвеером в суперскаляре надо пыхтеть.


                1. Armmaster
                  13.12.2024 15:50

                  Вы понятия не имеете, о чём говорите. Впрочем, как обычно.

                  Особенно смешно читать про "не видно преимуществ", когда современные и не очень ОоО ядра, имея существенно меньшую площадь, обладают в разы большой производительностью по сравнению с Vliw


                  1. OpenA
                    13.12.2024 15:50

                    А вы не умеете читать, я говорил про то что эльбрус с его вроде бы много команд за такт, не имеет преимуществ перед интел/амд которые хоть и не имеют столько устройств зато имеют длинный конвеер который умеют очень плотно набивать, + имеют 256битные вектора против 64 битных например у 8С, и как следствие невозможность эмулировать два avx/такт

                    Другое дело что нигде и не заявляется что 25 операций/такт кого то там порвут, заявляется что они обеспечивают высокую производительность, тесты алгоритмов шифрования показывают что эльбрус вполне себе приближается по производительности к вполне себе современным intel/амд с задействованными avx -ами

                    И вот эта возможность пусть на некоторых задачах выступать на уровне флагманов досталась относительно дешево, это нужно тоже учитывать.

                    современные и не очень ОоО ядра, имея существенно меньшую площадь, обладают в разы большой производительностью по сравнению с Vliw

                    если бы это было так тогда бы все использовали OoO в dsp, но почему то когда нужно что то быстро считать, затрачивая минимум энергии все выбирают vliw


                    1. Armmaster
                      13.12.2024 15:50

                      Я могу лишь повторить, что вы понятия не имеете, о чём говорите, поэтому вышенаписанное - это какой то винегрет, где спутаны глубина и ширина конвейера и много ещё чего другого. Давайте вы для начала хотя бы Харриса осилите. В конце концов, вы только на моей памяти уже минимум 3 года пытаетесь выступать со своими "идеями", но что-то прогресса в понимании не видно. Стыдно должно быть уже


                      1. OpenA
                        13.12.2024 15:50

                        Не нужно повторять, нужно приводить аргументы, я ничего не говорил про какую то глубину и ширину конвеера не надо мне приписывать какие то глупости которые я не говорил. Ни с какими идеями я так же не выступаю, просто читаю и отвечаю на комментарии. Все. Обратитесь к психотерапевту.


    1. VVS_AMD Автор
      13.12.2024 15:50

      Во-первых, Эльбрус 8С и Haswell практически одногодки. И с тех пор Эльбрусы прошли ещё два поколения. И по производительности они тоже хорошо прибавили. Во-вторых, Celeron G1820 не так уж и слаб в однопотоке. А тесты проводились как раз в однопотоке. Современные топовые ЦП, может, раза в три-четыре быстрее.
      Хотя с тем, что Эльбрусы сильно не дотягивают по производительности до современных топовых ЦП, спорить не стану.


      1. nixtonixto
        13.12.2024 15:50

        Этот Celeron на старте продаж стоил $42, причём в эту цену ещё входит графический ускоритель... Это сверхбюджетное решение, и сравнивать с ним что-то не сверхбюджетное - как минимум, некорректно.


        1. VVS_AMD Автор
          13.12.2024 15:50

          Внимание! Мы тестим однопоток! Это раз.
          Целерон тоже можно засунуть в четырёхпроцессорный сервер и получить в итоге 32 ядра? Он поддерживает ECC память? Статья не про "выбор самого выгодного ЦП на 2015 год". Это два.
          Ядрён, батон, какая цена?

          Эльбрус можно и с лампочкой сравнить, если речь пойдёт о содержании драг.металлов в электронных компонентах. Главное - оставаться в теме.


          1. nixtonixto
            13.12.2024 15:50

            Тогда почему вы сравниваете с целероном и ещё более древним FX, а не с серверными процессорами с поддержкой ECC-памяти?


            1. VVS_AMD Автор
              13.12.2024 15:50

              Я уже не знаю как ответить, честно. Тему статьи пробовали читать?


    1. Kassiy_Pontiy_Pilat
      13.12.2024 15:50

      Вы все померяли или как обычно блаблабла


    1. OpenA
      13.12.2024 15:50

      8C процессор 2015г


  1. redf1sh
    13.12.2024 15:50

    Не увидел у вас ссылки на исходный код. Как мне проверить ваши измерения?

    И так, лучшие настройки оптимизации для данного теста на платформах:

    • x86: Rust - O2, C++ - O3.

    Как-то бедно вы смотрели. Откроем список опций в GCC https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html сделаем поиск по "-f" и получим 900+ результатов. Грубо говоря половина будет -fno. Грубая оценка даёт вам 450 опций управления компиляцией. А вы рассмотрели только одну. Также не пробовали режимы компиляции с профилем. Не пробовали опции -march=, которые на Эльбрусе дают большой прирост.

    Я не понял какой компилятор вы использовали для Эльбруса.

    В сравнении gcc и llc, gcc оказался чуть эффективнее, но разница очень маленькая

    Это один и тот же компилятор. LCC является совместимым с GCC по опциям, чтобы облегчить жизнь пользователям по системе сборки. И на Эльбрусах gcc является алиасом lcc в системе. Так что если есть разница, то скорее всего это ошибка измерения.

    У меня вызывает вопрос следующее:

    C++: GCC v9.3.0 compatible; LLVM version 13.0.1.

    Какая версия LLVM у lcc, если его там нет? Или вы тестировали clang? А если нет, то зачем написали?


    1. VVS_AMD Автор
      13.12.2024 15:50

      Спасибо за дельный комментарий.
      Ссылки на исходники находятся в конце статьи в разделе "Ресурсы".
      Настроек компиляции действительно пробовал мало. Но -march=native пробовал и разницы не заметил. Предварительно почитал рекомендации МЦСТ. Из описанных там опций компиляции увидел смысл только в двухфазной компиляции с профилем. У прочих настроек не увидел пользы для моего кода. Компилировал двумя способами: 1) g++ -Ox -march=native main.cpp -o main; 2) cmake --build . Файл CmakeLists.txt можете найти в исходниках. g++ давал мизерный (десятые доли процента), но стабильный плюс со всеми режимами оптимизации. Так что ошибка измерений исключена.
      Допускаю, что мог напутать с lcc/gcc. Тут мои познания достаточно скромные. А версию LLVM я указал не случайно. Путь к этой самой LLVM прописан в cmake-файле.


      1. nick758
        13.12.2024 15:50

        Ссылки на исходники находятся в конце статьи в разделе "Ресурсы".

        Ссылку на C++ исправьте: https://gitlab.com/vvsamd/the-sieve-of-eratosthenes-in-c


        1. VVS_AMD Автор
          13.12.2024 15:50

          Благодарю. Поправил.


      1. redf1sh
        13.12.2024 15:50

        Я не спорю, что путь к LLVM прописан в CMakeLists.txt. Я не понимаю зачем. Вы сами написали, что собираете компилятором g++. LLVM никак не используется в вашем main.cpp. Зачем он тогда?

        Вижу, что прописан project(LLVMPassSample) что подсказывает мне CMakeFiles.txt взят от балды из интернета от студенческого репозитория с реализацией своего pass'a.

        cmake_minimum_required(VERSION 3.7)
        project(LLVMPassSample)
        
        set(CMAKE_CXX_STANDARD 17)
        
        set(LLVM_DIST_PATH "/usr/local/opt/llvm-clang/current"
                CACHE STRING "LLVM distribution install path")
        
        set(LLVM_DIR ${LLVM_DIST_PATH}/lib/cmake/llvm)
        find_package(LLVM REQUIRED CONFIG)
        message(STATUS "Using LLVM version ${LLVM_PACKAGE_VERSION}")
        list(APPEND CMAKE_MODULE_PATH ${LLVM_CMAKE_DIR})
        
        set(LLVM_RUNTIME_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/bin)
        set(LLVM_LIBRARY_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/lib)
        include(HandleLLVMOptions)
        include(AddLLVM)
        add_compile_options(-march=native)
        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
        
        add_definitions(${LLVM_DEFINITIONS})
        include_directories(${LLVM_INCLUDE_DIRS})
        
        add_executable(sieve_of_Eratosthenes main.cpp)
        


        1. VVS_AMD Автор
          13.12.2024 15:50

          Я же написал, ко компилировал двумя способами: g++ и cmake. И про мизерную разницу между компиляторами... как раз про это.

          CMakeFiles.txt взят от балды из интернета

          Не от балды, а как результат поиска вариантов использования LLVM в cmake. На статус спеца по C++ не претендую. И в статье об этом написал. У меня, как у java-разработчика с минимальными знаниями C++ и Rust, написав годный код на этих языках, стояла задача собрать годный исполняемый файл с применением самых профитных оптимизаций. Единственной ошибкой в этом вопросе пока неиспользование PGO. Исправляю этот момент.
          Огромная просьба! Прочитайте внимательнее статью и мои комментарии. А то вы уже начинаете с меня спрашивать то, чего я не писал...
          Даже тут:

          В сравнении gcc и llc, gcc оказался чуть эффективнее, но разница очень маленькая

          Это один и тот же компилятор. LCC является совместимым с GCC по опциям, чтобы облегчить жизнь пользователям по системе сборки. И на Эльбрусах gcc является алиасом lcc в системе.

          У меня написано про llc - LLVM static compiler. А вы мне про LCC...


          1. redf1sh
            13.12.2024 15:50

            Смешались в кучу кони, люди,

            Давайте я объясню подробнее в чём проблема.

            У меня написано про llc - LLVM static compiler. А вы мне про LCC...


            Возможно вы действительно не опечатались и пытались использовать транслятор llc, что уже само по себе вызывает вопрос. Это не полноценный компилятор для языка С++ на котором написана ваша программа. Это только бэкенд для трансляции LLVM-IR в ассемблер. У него нет фронтенда и запуска линкера. Вам нужен был драйвер компиляции. В проекте LLVM для языка C++ это clang++.

            Я же написал, ко компилировал двумя способами: g++ и cmake. И про мизерную разницу между компиляторами... как раз про это.

            cmake -- это не компилятор. Это система генерации файлов сборки для систем сборки (make, ninja, visual studio и т.д.). Она ничего не компилирует. Она создаёт инструкции для целевой системы сборки при выполнении которых и будет запущен компилятор.

            Не от балды, а как результат поиска вариантов использования LLVM в cmake. На статус спеца по C++ не претендую. И в статье об этом написал. У меня, как у java-разработчика с минимальными знаниями C++ и Rust, написав годный код на этих языках, стояла задача собрать годный исполняемый файл с применением самых профитных оптимизаций. Единственной ошибкой в этом вопросе пока неиспользование PGO. Исправляю этот момент.Огромная просьба! Прочитайте внимательнее статью и мои комментарии. А то вы уже начинаете с меня спрашивать то, чего я не писал...

            LLVM это не только компилятор. Это ИНФРАСТРУКТУРА для ПОСТРОЕНИЯ компиляторов. Я не просто так написал, что в CMakeFiles.txt у вас заготовка для своего пасса. И компилятор clang/LLVM (а именно так, так как вам нужен ещё и фронтенд) там не прописывается. Вы могли сами это проверить, но давайте я сделаю это за вас:

            $ lscpu
            Architecture:        e2k
            ...
            CPU family:          4
            Model name:          E8C
            CPU MHz:             1300
            $ git clone https://gitlab.com/vvsamd/the-sieve-of-eratosthenes-in-c
            $ cmake --version
            cmake version 3.15.4
            $ mkdir build && cd build
            $ cmake ..
            -- The C compiler identification is GNU 7.3.0
            -- The CXX compiler identification is GNU 7.3.0
            -- Check for working C compiler: /usr/bin/cc
            -- Check for working C compiler: /usr/bin/cc -- works
            ...
            -- Using LLVM version 9.0.1
            ...
            $ cmake --build . -v
            gmake[2]: Entering directory '/the-sieve-of-eratosthenes-in-c/build'
            [ 50%] Building CXX object CMakeFiles/sieve_of_Eratosthenes.dir/main.cpp.o
            /usr/bin/c++  -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS  -fPIC -fvisibility-inlines-hidden -Werror=date-time -std=c++11 -w -ffunction-sections -fdata-sections -O3   -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -march=native -std=gnu++1z -o CMakeFiles/sieve_of_Eratosthenes.dir/main.cpp.o -c /the-sieve-of-eratosthenes-in-c/main.cpp
            [100%] Linking CXX executable sieve_of_Eratosthenes
            /usr/bin/cmake -E cmake_link_script CMakeFiles/sieve_of_Eratosthenes.dir/link.txt --verbose=1
            /usr/bin/c++   -fPIC -fvisibility-inlines-hidden -Werror=date-time -std=c++11 -w -ffunction-sections -fdata-sections -O3   CMakeFiles/sieve_of_Eratosthenes.dir/main.cpp.o  -o sieve_of_Eratosthenes 
            gmake[2]: Leaving directory '/the-sieve-of-eratosthenes-in-c/build'
            
            $ c++ --version 
            lcc:1.25.10:Nov--7-2020:e2k-v4-linux
            gcc (GCC) 7.3.0 compatible

            Что здесь происходит?

            • Я выкачал с вашего gitlab ваш проект

            • Я выполнил его конфигурацию через cmake и получил сообщение, что будет использован компилятор lcc, совместимый с GCC 7.3.0

            • Я получил сообщение, что будет подключен проект LLVM (но не будет использован компилятор clang++/LLVM(!))

            • Я собрал проект и что видно по строкам компиляции, он собирался с помощью LCC

            Вы НЕ собрали ваше решето компилятором, связанным с LLVM. Вы подключили с помощью строк, связанных с LLVM всю необходимую инфраструктуру (заголовочные файлы, библиотеки (libllvm?)) для разработки своей фазы компиляции в составе LLVM. Чувствуете разницу?

            Чтобы собрать с clang/LLVM достаточно было выполнить:

            $ clang++ -O3 main.cpp -o main

            Тут даже cmake не нужен. Но если он вам так понравился, то можете написать CMakeLists.txt как-то так

            cmake_minimum_required(VERSION 3.7)
            project(the_sieve_of_eratosthenes_in_c)
            
            add_compile_options(-march=native)
            add_compile_options(-O3)
            
            add_executable(sieve_of_eratosthenes main.cpp)

            И собрать так проект так:

            $ mkdir build && cd build
            $ cmake .. -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang
            -- The C compiler identification is Clang 9.0.1
            -- The CXX compiler identification is Clang 9.0.1
            -- Check for working C compiler: /usr/bin/clang
            -- Check for working C compiler: /usr/bin/clang -- works
            ...
            $ cmake --build . -v
            [ 50%] Building CXX object CMakeFiles/sieve_of_eratosthenes.dir/main.cpp.o
            /usr/bin/clang++    -march=native -O3 -o CMakeFiles/sieve_of_eratosthenes.dir/main.cpp.o -c /the-sieve-of-eratosthenes-in-c/main.cpp
            /the-sieve-of-eratosthenes-in-c/main.cpp:168:5: warning: only one parameter on 'main' declaration [-Wmain]
            int main(int endValue) {
                ^
            1 warning generated.
            [100%] Linking CXX executable sieve_of_eratosthenes
            /usr/bin/cmake -E cmake_link_script CMakeFiles/sieve_of_eratosthenes.dir/link.txt --verbose=1
            /usr/bin/clang++     CMakeFiles/sieve_of_eratosthenes.dir/main.cpp.o  -o sieve_of_eratosthenes 

            Теперь мы собрали проект с помощью clang++/LLVM

            Давайте я и проведу сравнение (5*10^8 чисел):
            clang-9: -O3 3731мс
            Но мне clang не интересен.

            lcc 1.29: -O3 -march=elbrus-v4: 3364мс
            lcc 1.29: -O4 -march=elbrus-v4 -ffast -ffast-math: 2351мс
            Замечаем, что счётчики циклов 32-битные, когда мы работаем на 64-битах. Это заставит компилятор генерировать расширение. Замена int -> long int даёт:
            lcc 1.29: -O4 -march=elbrus-v4 -ffast -ffast-math + long int 2135мс
            Экспериментируем с опциями дальше
            lcc 1.29: -O4 -march=elbrus-v4 -ffast -ffast-math -fforce-loop-apb + long int: 2058мс
            lcc 1.29: -O4 -march=elbrus-v4 -ffast -ffast-math -fforce-loop-apb -fforce-vect -fforce-swp + long int: 2053мс

            Заметим, что есть проблемы с циклом на main.cpp:100. Пометим его #pragma swp

            lcc 1.29: -O4 -march=elbrus-v4 -ffast -ffast-math -fforce-loop-apb -fforce-vect -fforce-swp + long int + swp: 2033мс

            Судя по perf железо занято на 90.1%. Дальше ускорять уже нет ни желания, ни времени

            PGO тут не помогло. Здесь слишком мало кода и предсказатель профиля и так справился хорошо


    1. VVS_AMD Автор
      13.12.2024 15:50

      ...PGO попробую в ближайшее время. Допишу в статью, если будет толк.


  1. Armmaster
    13.12.2024 15:50

    К методике замеров нет смысла придираться, как говорится, что-то померяли и ладно. Единственное, было бы очень полезно хотя бы изначальный C/rust/Java код привести, а в идеале ещё и ассемблер для каждого варианта компиляции.


    1. Armmaster
      13.12.2024 15:50

      Так, вопрос по исходному коду снимается, в конце есть ссылки, но они сливаются с тегами в конце статьи, из-за чего их совсем не заметно. Лучше в начало вынести. По ассемблеру пожелание остаётся в силе