Go быстрее C++ с вычислениями и вложенными повторяющимися циклами | Где моя ошибка?

Вопрос или проблема

Итак, я написал функцию, чтобы проверить, равны ли все элементы массива в C++:

#include <vector>

static inline int all_equal(const std::vector<int> &vals) {
  if (vals.size() < 1) {
    return -1;
  }
  int sum = 0;
  for (const int &v : vals) {
    sum += v;
  }
  if (sum == 0) {
    return 3;
  }
  return vals.size() * vals[0] == sum;
}

void bench(void) {
  std::vector<int> a(10'000'000, 100);
  all_equal(a);
  // std::cout << "равно? " << all_equal(a) << "\n";
}

int main(void) {
  for (int i = 0; i < 100; i++) {
    bench();
  }
  return 0;
}

с следующими флагами компиляции

g++ -o cppmain -O3 -ffast-math main.cc

Я замерил время с помощью time и получил следующие результаты

* time ./cppmain
реальное  0m1.829s
пользовательское  0m0.462s
системное  0m1.367s

Мне было интересно, как Go справится с этой же программой, поэтому я написал это

package main

// import "C" // Чтобы остановить LSP от жалоб на cpp файлы.

func all_eqaul(v *[]int) int {
    if len(*v) < 1 {
        return -1
    }
    var sum int
    for _, v := range *v {
        sum += v
    }
    if sum == 0 {
        return 3
    }
    if sum == len(*v)*(*v)[0] {
        return 1
    }
    return 0
}

func bench() {
    s := make([]int, 10_000_000)
    for i := range s {
        s[i] = 100
    }
    // fmt.Println("равно: ", all_eqaul(&s))
    all_eqaul(&s)
}

func main() {
    for i := 0; i < 100; i++ {
        bench()
    }
}

и скомпилировал с помощью

go build -o gomain main.go

К моему удивлению, когда я замерил время, я получил

* time ./gomain
реальное  0m1.640s
пользовательское  0m1.562s
системное  0m0.109s

Я не знаю, что я сделал неправильно или правильно ли я интерпретирую вывод time, но как так получилось, что Go быстрее C++ на 200 мс?

Ответ или решение

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

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

Анализ кода на C++

В вашем коде на C++ вы проверяете, все ли элементы массива равны, суммируя значения элементов и сравнивая сумму с произведением первого элемента и длины массива. Вот основные моменты:

  1. Компиляция с оптимизациями: Вы компилировали C++ код с флагами -O3 -ffast-math, что включает агрессивные оптимизации. Однако, могли возникнуть ситуации, когда оптимизатор не смог выжать максимум из кода, или в некоторых случаях может быть игнорированы оптимизации из-за сложности конструкции кода.

  2. Пик производительности: Ваша функция all_equal вызывает суммирование всех элементов массива, что занимает O(n) времени. Однако это по сути также делает и версия на Go.

Анализ кода на Go

  1. Простота кода: Код на Go создаёт массив и заполняет его в течение O(n) времени, а затем также выполняет сумму. Однако Go компилятор может иметь более эффективные алгоритмы для некоторых операций, особенно для Go 1.18 и выше, что может обеспечивать более быструю работу с массивами на больших объемах данных.

  2. Оптимизация зборщика мусора: Go управляет памятью по-другому, и эффективность управления памятью может позволить избегать незначительных задержек, которые могут возникать в C++ из-за различных аспектов управления памятью.

Возможные причины разницы в производительности:

  1. Разные подходы к памяти: Go может использовать более эффективные методы для работы с массивами, в то время как C++ может исполнять дополнительные инструкции или затраты на управление памятью в вашем коде.

  2. Оптимизация выполнения: Возможно, компилятор Go в вашей версии использовал оптимизации, которые были не доступны компилятору C++ в вашем случае.

  3. Запуск программы: Время работы программы также зависит от условий на момент выполнения. Ваша система может иметь разные нагрузки в момент запуска обеих программ, что тоже могло влиять на результаты.

Рекомендации

  1. Профилирование кода: Используйте инструменты профилирования для глубокого анализа вашего кода и определения узких мест как в C++, так и в Go.

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

  3. Тестирование при разных условиях: Запускайте тесты несколько раз, чтобы учесть любые колебания производительности, и делайте сравнения в аналогичных условиях.

  4. Использование SIMD: Рассмотрите возможность использования SIMD-инструкций при работе с большими массивами в C++ для повышения производительности.

Этот анализ показывает, что разница в производительности не обязательно указывает на недостатки одного языка или преимуществ другого. Необходимо углублённо изучать результаты, и оптимизация всегда находится на первом месте для достижения лучших результатов.

Оцените материал
Добавить комментарий

Капча загружается...