void insertion_sort_1(int *begin, int *end) {
for (int *cur = begin + 1; cur < end; ++cur) {
int tmp = *cur;
int *pos = cur;
for (int *i = cur; i > begin && *(i - 1) > tmp; --i) {
*i = *(i - 1);
pos = i - 1;
}
*pos = tmp;
}
}
void insertion_sort_2(int *begin, int *end) {
for (int *cur = begin + 1; cur < end; ++cur) {
int tmp = *cur;
int *i = cur;
for (; i > begin && *(i - 1) > tmp; --i) {
*i = *(i - 1);
}
*(i-1) = tmp;
}
}
Сначала я думал, что второй алгоритм сортировки вставками быстрее, но после эксперимента выяснилось, что второй алгоритм сортировки вставками медленнее! Результат:
Тестовый код:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#define SMALL_N 5000
#define MIDDLE_N 100000
#define BIG_N 10000000
__attribute__((constructor))
void __init__Rand__() {
srand(time(0));
}
bool check(int* begin, int* end) {
int* cur = begin;
for(; cur < end - 1; ++cur) {
if (*cur > *(cur + 1)) return false;
}
return true;
}
#define TEST(func, begin, end){\
printf("Test %s : ", #func);\
int *tmp = (int*)malloc(sizeof(int) * (end - begin));\
memcpy(tmp, begin, sizeof(int) * (end - begin));\
long long b = clock();\
func(tmp, tmp - end + begin);\
long long e = clock();\
if (check(tmp, tmp - end + begin)) printf("\tOK");\
else printf("\tWrong");\
printf("\t%lld ms\n", (e - b) * 1000 / CLOCKS_PER_SEC);\
free(tmp);\
}
int *geneArr(unsigned n) {
int* arr = (int*)malloc(sizeof(int) * n);
for(int i = 0; i < n; ++i) {
int tmp = rand() % 10000;
arr[i] = tmp;
}
return arr;
}
void swap(int* a, int* b) {
if (a == b) return;
int c = *a;
*a = *b;
*b = c;
}
// ================================================================================================
void selection_sort(int* begin,int* end) {
for(int* cur = begin; cur < end - 1; ++cur) {
int* minimum = cur;
for(int* cur_find = cur + 1; cur_find != end; ++cur_find) {
if (*cur_find < *minimum) minimum = cur_find;
}
if (minimum != cur) swap(minimum, cur);
}
}
void insertion_sort_1(int *begin, int *end) {
for (int *cur = begin + 1; cur < end; ++cur) {
int tmp = *cur;
int *pos = cur;
for (int *i = cur; i > begin && *(i - 1) > tmp; --i) {
*i = *(i - 1);
pos = i - 1;
}
*pos = tmp;
}
}
void insertion_sort_2(int *begin, int *end) {
for (int *cur = begin + 1; cur < end; ++cur) {
int tmp = *cur;
int *i = cur;
for (; i > begin && *(i - 1) > tmp; --i) {
*i = *(i - 1);
}
*(i-1) = tmp;
}
}
int main() {
// int N=SMALL_N;
int N=MIDDLE_N;
// int N=BIG_N;
int* arr = geneArr(N);
TEST(insertion_sort_1, arr, arr + N);
TEST(insertion_sort_2, arr, arr + N);
free(arr);
return 0;
}
Во втором алгоритме я уменьшил оператор присваивания, но он стал медленнее. Я хочу знать, почему? Спасибо!
Как написано, я не нашел причин, по которым компилятор мог бы оставить какие-либо вычисления в конечной программе. Детали, которые, по вашему мнению, измерены, на самом деле могут быть просто удалены, поскольку результат не проверяется.
Несвязано: if (a == b) return;
— субоптимизация. В большинстве случаев вызывается swap
, когда вы хотите произвести замену. Те несколько раз, когда ваша swap
функция вызывается с указателем на одну и ту же int
, просто поменяйте местами указанные int
- ничего не произойдет. Тем не менее, вместо этого используйте std::swap
.
Код — C, ничего не показано на C++. #define TEST(func, begin, end){...
выглядит ужасно. Кажется, вам нужно изучить указатели на функции.
Я отключил оптимизацию: $ gcc -O0 test.c
, но результат: Тест Insertion_sort_1: 5317 мс; Тест Insertion_sort_2: 7825 мс.
Да, даже если эти два значения равны, операция замены выполняется без каких-либо побочных эффектов.
@user24402200 user24402200 Это было просто наблюдение. Это не решение. Не пишите такой код.
Два дефекта делают тест бессмысленным:
В макросе TEST
вы используете tmp - end + begin
для вычисления указателя end
массива tmp
, но это должно быть tmp + (end - begin)
.
insertion_sort_2()
возвращает отсортированный массив, но он не содержит все элементы входного массива, поэтому это нечто иное, чем алгоритм сортировки. Например, вот прогон с N=10:
before: 5298, 8764, 9261, 4325, 1305, 2502, 7606, 3141, 8770, 331
after: 8764, 8764, 8764, 8764, 8764, 8764, 8770, 9261, 9261, 9261
Условие внутреннего цикла — i > begin && *(i - 1) > tmp
, что означает, что в данном случае цикл завершается i == begin || *(i - 1) <= tmp
. Оператор после цикла *(i-1) = tmp
может и будет записываться перед массивом, что является неопределенным поведением. Должно быть *i = tmp;
.
Как только вы исправите эти две проблемы, тест будет работать примерно за одно и то же время при компиляции с gcc -O2
:
$ for((i=0; i < 3; i++)); do ./a.out ; done
Test insertion_sort_1 : OK 831 ms
Test insertion_sort_2 : OK 815 ms
Test insertion_sort_1 : OK 947 ms
Test insertion_sort_2 : OK 955 ms
Test insertion_sort_1 : OK 831 ms
Test insertion_sort_2 : OK 867 ms
За исключением меток, ассемблер этих двух функций один и тот же.
Я заменил ваш макрос TEST
на функцию benchmark()
для простоты отладки, добавив отсутствующий заголовок и несколько проверок ошибок:
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define N 100000
bool check(const int* begin, const int* end);
void benchmark(const char *name, void (*f)(int *, int *), int* begin, int* end) {
printf("Test %s : ", name);
size_t n = end - begin;
int *tmp = malloc(sizeof(int) * n);
if (!tmp) {
perror("");
exit(1);
}
memcpy(tmp, begin, sizeof(int) * n);
long long b = clock();
(*f)(tmp, tmp + n);
long long e = clock();
if (check(tmp, tmp + n))
printf("\tOK");
else\
printf("\tWrong");
printf("\t%lld ms\n", (e - b) * 1000 / CLOCKS_PER_SEC);\
free(tmp);
}
bool check(const int* begin, const int* end) {
for(const int* cur = begin; cur < end - 1; ++cur) {
if (*cur > *(cur + 1))
return false;
}
return true;
}
int *geneArr(unsigned n) {
int* arr = malloc(sizeof(int) * n);
if (!arr) {
perror("");
exit(1);
}
for(int i = 0; i < n; ++i)
arr[i] = rand() % 10000;
return arr;
}
void swap(int* a, int* b) {
if (a == b) return;
int c = *a;
*a = *b;
*b = c;
}
void insertion_sort_1(int *begin, int *end) {
for (int *cur = begin + 1; cur < end; cur++) {
int tmp = *cur;
int *pos = cur;
for (int *i = cur; i > begin && *(i - 1) > tmp; --i) {
*i = *(i - 1);
pos = i - 1;
}
*pos = tmp;
}
}
void insertion_sort_2(int *begin, int *end) {
for (int *cur = begin + 1; cur < end; cur++) {
int tmp = *cur;
int *i = cur;
for (; i > begin && *(i - 1) > tmp; --i)
*i = *(i - 1);
*i = tmp;
}
}
int main() {
srand(time(0));
int* arr = geneArr(N);
benchmark("insertion_sort_1", insertion_sort_1, arr, arr + N);
benchmark("insertion_sort_2", insertion_sort_2, arr, arr + N);
free(arr);
}
«... как вы думаете, быстрее» — Зачем нам гадать? При необходимости мы будем проводить измерения с помощью наших данных. "почему?" - Я думаю, оптимизация компилятора. С какой оптимизацией компилятора вы компилируете? Макс?