Похоже, что Java выполняет алгоритмы «голыми руками» быстрее, чем C ++. Зачем? - PullRequest
6 голосов
/ 21 июня 2011

Введение:

Используя два идентичных алгоритма сортировки слиянием, я проверил скорость выполнения C ++ (с помощью Visual Studios C ++ 2010 express) и Java (с использованием NetBeans 7.0). Я предположил, что выполнение C ++ будет, по крайней мере, немного быстрее, но тестирование показало, что выполнение C ++ было в 4-10 раз медленнее, чем выполнение Java. Я считаю, что я установил все оптимизации скорости для C ++, и я публикую как выпуск, а не как отладку. Почему происходит это несоответствие скорости?

Код:

Java:

public class PerformanceTest1
{
 /**
  * Sorts the array using a merge sort algorithm
  * @param array The array to be sorted
  * @return The sorted array
  */
 public static void sort(double[] array)
 {
      if(array.length > 1)
      {
           int centre;
           double[] left;
           double[] right;
           int arrayPointer = 0;
           int leftPointer = 0;
           int rightPointer = 0;

           centre = (int)Math.floor((array.length) / 2.0);

           left = new double[centre];
           right = new double[array.length - centre];

           System.arraycopy(array,0,left,0,left.length);
           System.arraycopy(array,centre,right,0,right.length);

           sort(left);
           sort(right);

           while((leftPointer < left.length) && (rightPointer < right.length))
           {
                if(left[leftPointer] <= right[rightPointer])
                {
                     array[arrayPointer] = left[leftPointer];
                     leftPointer += 1;
                }
                else
                {
                     array[arrayPointer] = right[rightPointer];
                     rightPointer += 1;
                }
                arrayPointer += 1;
           }
           if(leftPointer < left.length)
           {
                System.arraycopy(left,leftPointer,array,arrayPointer,array.length - arrayPointer);
           }
           else if(rightPointer < right.length)
           {
                System.arraycopy(right,rightPointer,array,arrayPointer,array.length - arrayPointer);
           }
      }
 }

 public static void main(String args[])
 {
      //Number of elements to sort
      int arraySize = 1000000;

      //Create the variables for timing
      double start;
      double end;
      double duration;

      //Build array
      double[] data = new double[arraySize];
      for(int i = 0;i < data.length;i += 1)
      {
           data[i] = Math.round(Math.random() * 10000);
      }

      //Run performance test
      start = System.nanoTime();
      sort(data);
      end = System.nanoTime();

      //Output performance results
      duration = (end - start) / 1E9;
      System.out.println("Duration: " + duration);
 }
}

C ++:

#include <iostream>
#include <windows.h>
using namespace std;

//Mergesort
void sort1(double *data,int size)
{
if(size > 1)
{
    int centre;
    double *left;
    int leftSize;
    double *right;
    int rightSize;
    int dataPointer = 0;
    int leftPointer = 0;
    int rightPointer = 0;

    centre = (int)floor((size) / 2.0);
    leftSize = centre;
    left = new double[leftSize];
    for(int i = 0;i < leftSize;i += 1)
    {
        left[i] = data[i];
    }
    rightSize = size - leftSize;
    right = new double[rightSize];
    for(int i = leftSize;i < size;i += 1)
    {
        right[i - leftSize] = data[i];
    }

    sort1(left,leftSize);
    sort1(right,rightSize);

    while((leftPointer < leftSize) && (rightPointer < rightSize))
    {
        if(left[leftPointer] <= right[rightPointer])
        {
            data[dataPointer] = left[leftPointer];
            leftPointer += 1;
        }
        else
        {
            data[dataPointer] = right[rightPointer];
            rightPointer += 1;
        }
        dataPointer += 1;
    }
    if(leftPointer < leftSize)
    {
        for(int i = dataPointer;i < size;i += 1)
        {
            data[i] = left[leftPointer++];
        }
    }
    else if(rightPointer < rightSize)
    {
        for(int i = dataPointer;i < size;i += 1)
        {
            data[i] = right[rightPointer++];
        }
    }
            delete left;
            delete right;
}
}

void main()
{
//Number of elements to sort
int arraySize = 1000000;

//Create the variables for timing
LARGE_INTEGER start; //Starting time
LARGE_INTEGER end; //Ending time
LARGE_INTEGER freq; //Rate of time update
double duration; //end - start
QueryPerformanceFrequency(&freq); //Determinine the frequency of the performance counter (high precision system timer)

//Build array
double *temp2 = new double[arraySize];
QueryPerformanceCounter(&start);
srand((int)start.QuadPart);
for(int i = 0;i < arraySize;i += 1)
{
    double randVal = rand() % 10000;
    temp2[i] = randVal;
}

//Run performance test
QueryPerformanceCounter(&start);
sort1(temp2,arraySize);
QueryPerformanceCounter(&end);
    delete temp2;

//Output performance test results
duration = (double)(end.QuadPart - start.QuadPart) / (double)(freq.QuadPart);
cout << "Duration: " << duration << endl;

//Dramatic pause
system("pause");
}

Замечания:

Для 10000 элементов выполнение C ++ занимает примерно в 4 раза больше времени, чем выполнение Java. Для 100000 элементов это соотношение составляет около 7: 1. Для 10000000 элементов это соотношение составляет около 10: 1. На протяжении более 10000000 выполнение Java завершается, но выполнение C ++ останавливается, и мне приходится вручную завершать процесс.

Ответы [ 8 ]

15 голосов
/ 21 июня 2011

Я думаю, что в том, как вы запустили программу, может быть ошибка.Когда вы нажмете F5 в Visual C ++ Express , программа будет запущена в режиме отладчика, и она будет НАМНОГО медленнее.В других версиях Visual C ++ 2010 (например, Ultimate, который я использую), попробуйте нажать сочетание клавиш CTRL + F5 (т. Е. Запустить без отладки) или запустить сам исполняемый файл (в Express), и вы увидите разницу.

Я запускаю вашу программу только с одной модификацией на моем компьютере (добавлено delete[] left; delete[] right;, чтобы избавиться от утечки памяти; в противном случае в 32-битном режиме не хватило бы памяти!).У меня i7 950. Честно говоря, я также передал тот же массив в Arrays.sort () в Java и в std :: sort в C ++.Я использовал размер массива 10 000 000.

Вот результаты (время в секундах):

Java code:            7.13
Java Arrays.sort:     0.93

32 bits
C++ code:             3.57
C++ std::sort         0.81

64 bits
C++ code:             2.77
C++ std::sort         0.76

Так что код на C ++ намного быстрее и даже стандартная библиотека, которая сильно настроенакак в Java, так и в C ++ имеет тенденцию показывать небольшое преимущество для C ++.

Редактировать: я только что понял, что в вашем исходном тесте вы запускаете код C ++ в режиме отладки.Вы должны переключиться в режим Release и запустить его вне отладчика (как я объяснил в моем посте), чтобы получить честный результат.

10 голосов
/ 21 июня 2011

Я не программирую C ++ профессионально (или даже непрофессионально :), но я замечаю, что вы выделяете double в куче (double * temp2 = new double [arraySize];).Это дорого по сравнению с инициализацией Java, но что более важно, это утечка памяти, так как вы никогда не удаляете ее, это может объяснить, почему ваша реализация C ++ останавливается, в основном она исчерпывает память.

5 голосов
/ 21 июня 2011

Для начала вы пытались использовать std::sort (или std::stable_sort, который обычно является mergesort), чтобы получить базовую производительность в C ++?

Я не могу комментировать код Java, но для C ++код:

  • В отличие от Java, new в C ++ требует ручного вмешательства для освобождения памяти.В каждой рекурсии у тебя будет течь память.Вместо этого я бы предложил использовать std::vector, поскольку он управляет всей памятью, а конструктор iterator, iterator даже сделает копию (и, возможно, оптимизирован лучше, чем ваш цикл for).Это почти наверняка является причиной вашей разницы в производительности.
  • Вы используете arraycopy в Java, но не используете библиотечное средство (std::copy) в C ++, хотя, опять же, это не имеет значения, если вы используете vector.
  • Nit: объявите и инициализируйте вашу переменную в той же строке, в той точке, в которой они вам нужны, но не в верхней части функции.
  • Если вам разрешеноиспользуйте части стандартной библиотеки, std::merge может заменить ваш алгоритм объединения.

EDIT: Если вы действительно используете скажем delete left; для очистки памяти, это, вероятно, ваша проблема.Правильный синтаксис будет delete [] left; для освобождения массива.

4 голосов
/ 21 июня 2011

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

Я уверен, что время было потрачено на перерасход памяти.
Перепишите его, чтобы использовать стандартные объекты C ++ для управления памятью std ::вектор и посмотрим, что получится.

Лично я бы все-таки ожидал, что Java-версия победит (просто).Поскольку JIT допускает оптимизацию для конкретной машины, и хотя C ++ может выполнять оптимизацию для конкретной машины, в целом он будет выполнять только общие оптимизации архитектуры (если вы не укажете точные флаги архитектуры).

  • Примечание: не забудьтескомпилировать с включенными оптимизациями.

Просто очистить ваш C ++:
Я не пытался сделать хорошую сортировку слиянием (просто переписанную) в стиле C ++

void sort1(std::vector<double>& data)
{
    if(data.size() > 1)
    {
        std::size_t         centre    = data.size() / 2;
        std::size_t         lftSize   = centre;
        std::size_t         rhtSize   = data.size() - lftSize;

        // Why are we allocating new arrays here??
        // Is the whole point of the merge sort to do it in place?
        // I forget bbut I think you need to go look at a knuth book.
        //
        std::vector<double> lft(data.begin(),           data.begin() + lftSize);
        std::vector<double> rht(data.begin() + lftSize, data.end());

        sort1(lft);
        sort1(rht);
        std::size_t dataPointer   = 0;
        std::size_t lftPointer    = 0;
        std::size_t rhtPointer    = 0;

        while((lftPointer < lftSize) && (rhtPointer < rhtSize))
        {                                                                               
            data[dataPointer++] = (lft[lftPointer] <= rht[rhtPointer])
                                    ?  lft[lftPointer++]
                                    :  rht[rhtPointer++];
        }
        std::copy(lft.begin() + lftPointer, lft.end(), &data[dataPointer]);
        std::copy(rht.begin() + rhtPointer, rht.end(), &data[dataPointer]);
    }
}

Думая о слиянии сортировки.Я бы попробовал это:
Я не проверял это, поэтому он может работать неправильно.Но это попытка не продолжать выделять огромные объемы памяти для выполнения сортировки.вместо этого он использует одну временную область и копирует результат обратно, когда сортировка завершена.

void mergeSort(double* begin, double* end, double* tmp)
{
    if (end - begin <= 1)
    {   return;
    }

    std::size_t size    = end - begin;
    double*     middle  = begin +  (size / 2);

    mergeSort(begin, middle, tmp);
    mergeSort(middle, end, tmp);

    double* lft    = begin;
    double* rht    = middle;
    double* dst    = tmp;
    while((lft < middle) && (rht < end))
    {
        *dst++  = (*lft < *rht)
                        ? *lft++
                        : *rht++;
    }
    std::size_t count   = dst - tmp;
    memcpy(begin,          tmp, sizeof(double) * count);
    memcpy(begin + count,  lft, sizeof(double) * (middle - lft));
    memcpy(begin + count,  rht, sizeof(double) * (end    - rht));
}

void sort2(std::vector<double>& data)
{
    double*     left    = &data[0];
    double*     right   = &data[data.size()];

    std::vector<double> tmp(data.size());

    mergeSort(left,right, &tmp[0]);
}
2 голосов
/ 21 июня 2011

Пара вещей.

Java сильно оптимизирована, и после выполнения кода один раз JIT-компилятор выполняет код как собственный.

Ваша System.arraycopy в Java будет выполняться намного быстрее, чем простое копирование каждого элементаодин за раз.попробуйте заменить эту копию на memcpy, и вы увидите, что она намного быстрее.

РЕДАКТИРОВАТЬ: посмотрите на этот пост: производительность C ++ по сравнению с Java / C #

1 голос
/ 21 июня 2011

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

0 голосов
/ 18 января 2012

Попробуйте создать глобальный вектор в качестве буфера и постарайтесь не выделять много памяти.Это будет выполняться быстрее, чем ваш код, потому что, если используются некоторые приемы (используется только один буфер и память выделяется при запуске программы, поэтому память не будет фрагментирована):

#include <cstdio>
#define N 500001

int a[N];
int x[N];
int n;

void merge (int a[], int l, int r)
{
    int m = (l + r) / 2;
    int i, j, k = l - 1;
    for (i = l, j = m + 1; i <= m && j <= r;)
        if (a[i] < a[j])
            x[++k] = a[i++];
        else
            x[++k] = a[j++];
    for (; i <= m; ++i)
        x[++k] = a[i];
    for (; j <= r; ++j)
        x[++k] = a[j];
    for (i = l; i <= r; ++i)
        a[i] = x[i];
}

void mergeSort (int a[], int l, int r)
{
    if (l >= r)
        return;
    int m = (l + r) / 2;
    mergeSort (a, l, m);
    mergeSort (a, m + 1, r);
    merge (a, l, r);
}

int main ()
{
    int i;
    freopen ("algsort.in", "r", stdin);
    freopen ("algsort.out", "w", stdout);
    scanf ("%d\n", &n);
    for (i = 1; i <= n; ++i)
        scanf ("%d ", &a[i]);
    mergeSort (a, 1, n);
    for (i = 1; i <= n; ++i)
        printf ("%d ", a[i]);
    return 0;
}
0 голосов
/ 21 июня 2011

Я не знаю, почему Java здесь намного быстрее.

Я сравнил его со встроенным Arrays.sort (), и он снова был в 4 раза быстрее.(Он не создает никаких объектов).

Обычно, если есть тест, где Java намного быстрее, потому что Java намного лучше удаляет код, который ничего не делает.

Возможно, вы могли бы использовать memcpy вместо цикла в конце.

...