Найшвидший спосіб скинути кожне значення std :: vector <int> до 0


198

Який найшвидший спосіб скинути кожне значення a std::vector<int>до 0 і зберегти початковий розмір векторів?

A для циклу з оператором []?



1
"Найшвидший" як у виконанні? Або як найпростіше втілити / підтримувати?
Загальний

Відповіді:


340
std::fill(v.begin(), v.end(), 0);

48
Дивлячись на висновок збірки, gcc фактично розгортає цю петлю за допомогою регістрів mmx, щоб скидати в 16 байт за один раз, поки вона не наблизиться до кінця. Я б сказав, що це досить швидко. Версія memset переходить до мемсету, що, напевно, приблизно так само швидко. Я б використовував ваш метод.
всезначний

Але перехід до мемсету - це одна інструкція, тому використання її призведе до меншого розміру бінарних файлів.
Олександр Шишенко

2
ОП це не саме те, про що просили, але просто перепризначення вектора на новий такого ж розміру ( v = std::vector<int>(vec_size,0)) здається трохи швидшим, ніж fillна моїй машині
Yibo Yang

1
Це самий ідіоматичний спосіб зробити це, більше ідіоматичний, ніж використання assign.
alfC

1
чи привласнення його новому вектору виділяє купу? а потім відмінити виділення існуючого вектора? Я міг бачити, що це повільніше, ніж мемсет та ін.
Конрад Джонс

150

Як завжди, коли ви запитуєте про найшвидший: Міряйте! Використовуючи вищезазначені методи (на Mac за допомогою Clang):

Method      |  executable size  |  Time Taken (in sec) |
            |  -O0    |  -O3    |  -O0      |  -O3     |  
------------|---------|---------|-----------|----------|
1. memset   | 17 kB   | 8.6 kB  | 0.125     | 0.124    |
2. fill     | 19 kB   | 8.6 kB  | 13.4      | 0.124    |
3. manual   | 19 kB   | 8.6 kB  | 14.5      | 0.124    |
4. assign   | 24 kB   | 9.0 kB  | 1.9       | 0.591    |

використовуючи 100000 ітерацій на вектор 10000 ints.

Редагувати: Якщо зміна цих цифр правдоподібно змінить отримані часи, ви можете мати певну впевненість (не так добре, як перевірка остаточного коду збірки), що штучний показник не був повністю оптимізований. Звичайно, найкраще зіпсувати виступ у реальних умовах. завершити Редагувати

для довідки використаний код:

#include <vector>

#define TEST_METHOD 1
const size_t TEST_ITERATIONS = 100000;
const size_t TEST_ARRAY_SIZE = 10000;

int main(int argc, char** argv) {

   std::vector<int> v(TEST_ARRAY_SIZE, 0);

   for(size_t i = 0; i < TEST_ITERATIONS; ++i) {
   #if TEST_METHOD == 1 
      memset(&v[0], 0, v.size() * sizeof v[0]);
   #elif TEST_METHOD == 2
      std::fill(v.begin(), v.end(), 0);
   #elif TEST_METHOD == 3
      for (std::vector<int>::iterator it=v.begin(), end=v.end(); it!=end; ++it) {
         *it = 0;
      }
   #elif TEST_METHOD == 4
      v.assign(v.size(),0);
   #endif
   }

   return EXIT_SUCCESS;
}

Висновок: використовуйте std::fill(бо, як інші сказали, це саме ідіоматичне)!


3
+1. Цей конкретний орієнтир не є переконливим, але точка абсолютно коректна, ви повинні написати тест на ефективність альтернатив, оскільки вони фактично будуть використовуватися. Якщо немає різниці в продуктивності, використовуйте те, що є найпростішим джерелом.
Стів Джессоп

3
"... не переконливо ..." ІМО, ця непереконливість сама по собі вже є хорошим моментом для здійснення орієнтирів, частіше за все Оптимізатор вже дуже добре справляється з тими ситуаціями, про які ставилася ОП. І я б змінив ваше останнє речення, щоб прочитати "Якщо немає суттєвої різниці в ефективності ..."
Фабіо Фракассі

4
ОНОВЛЕННЯ Використання Ноніуса для орієнтирів: clang3.6-libc ++ - c ++ 1y-O3 , gcc4.9-c ++ 1y-O3 та gcc5-c ++ 1y-O3 - TL; DR : assignповільніше, за винятком невеликих потужностей на libc++. КОД coliru / paste
15:15

2
Крім того, уау, якщо ви дбаєте про швидкість без оптимізацій (що може бути правдоподібно, якщо ви розгортаєтесь у режимі "налагодження", як це роблять деякі команди), fillвиглядає жахливо. Це на два порядки повільніше в цьому тесті.
Кайл Странд

5
@KyleStrand: Це не те, що заповнення жахливо, це шаблон і код генерується з -00 всередині вашого перекладацького блоку. Коли ви використовуєте memset, ви використовуєте код libc, який був складений з -O3 (навіть коли ви компілюєте свій код з -O0). Якщо вам важлива швидкість налагодження, і ви використовуєте шаблони, вам доведеться використовувати явні екземпляри шаблонів в окремому файлі, який ви компілюєте з -O3
Tic

25

Як щодо функції assignчлена?

some_vector.assign(some_vector.size(), 0);

2
ОП хотів скинути існуючі значення, але ваша відповідь краще, коли ви хочете змінити розмір і скинути значення. Дякую!

15

Якщо це просто вектор цілих чисел, я спершу спробую:

memset(&my_vector[0], 0, my_vector.size() * sizeof my_vector[0]);

Це не дуже C ++, тому я впевнений, що хтось забезпечить правильний спосіб цього. :)


3
Оскільки стандарт (2003 TC1) гарантує, що std :: вектор є суміжним у пам'яті, це повинно бути добре. Якщо ваша бібліотека c ++ не відповідає стандарту TC1 2003 року, не використовуйте це.
Маріо

2
@Mario: Я б не опублікував це, якщо тільки це було правдою і, звичайно, вважали б загальновідомим. :) Але спасибі
розмотайте

1
Я перевірив збірку. ::std::fillМетод розширюється до чого - то , що це чертовски швидко, хоча і трохи по кодовою bloaty стороні , так як це все інлайн. Я б все одно користувався цим, хоча читати набагато приємніше.
всезначний

4
Ви б краще додати перевірити, чи вектор порожній, і нічого не робити в цьому випадку. Обчислення & buf [0] для порожнього вектора може генерувати твердження в коді STL.
Сергій

4

спробуйте

std::fill

і також

std::size siz = vec.size();
//no memory allocating
vec.resize(0);
vec.resize(siz, 0);

розмір дуже приємний
Nick

3

У мене було те саме питання, але щодо досить короткого vector<bool>(afaik стандарт дозволяє реалізувати його внутрішньо інакше, ніж просто безперервний масив булевих елементів). Отже, я повторив трохи модифіковані тести Фабіо Фракассі. Результати такі (рази, в секундах):

            -O0       -O3
         --------  --------
memset     0.666     1.045
fill      19.357     1.066
iterator  67.368     1.043
assign    17.975     0.530
for i     22.610     1.004

Тож мабуть для цих розмірів vector<bool>::assign()швидше. Код, який використовується для тестів:

#include <vector>
#include <cstring>
#include <cstdlib>

#define TEST_METHOD 5
const size_t TEST_ITERATIONS = 34359738;
const size_t TEST_ARRAY_SIZE = 200;

using namespace std;

int main(int argc, char** argv) {

    std::vector<int> v(TEST_ARRAY_SIZE, 0);

    for(size_t i = 0; i < TEST_ITERATIONS; ++i) {
#if TEST_METHOD == 1
        memset(&v[0], false, v.size() * sizeof v[0]);
#elif TEST_METHOD == 2
        std::fill(v.begin(), v.end(), false);
   #elif TEST_METHOD == 3
        for (std::vector<int>::iterator it=v.begin(), end=v.end(); it!=end; ++it) {
            *it = 0;
        }
   #elif TEST_METHOD == 4
      v.assign(v.size(),false);
   #elif TEST_METHOD == 5
      for (size_t i = 0; i < TEST_ARRAY_SIZE; i++) {
          v[i] = false;
      }
#endif
    }

    return EXIT_SUCCESS;
}

Я використовував компілятор GCC 7.2.0 на Ubuntu 17.10. Командний рядок для складання:

g++ -std=c++11 -O0 main.cpp
g++ -std=c++11 -O3 main.cpp
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.