C ++ valarray проти вектора


159

Мені дуже подобаються вектори. Вони витончені і швидкі. Але я знаю, що ця річ під назвою валярій існує. Чому я використовую валярій замість вектора? Я знаю, що у валярій є синтаксичний цукор, але крім них, коли вони корисні?


2
Просто розмірковував над цим і днями. Наскільки я знаю, це дійсно так само спеціалізований математичний вектор.
GManNickG

Чи не шаблони виразів valarray не роблять?
Mooing Duck

Фізик Ульріх Мутце надає приклад використання valarray тут і тут
життєвий баланс

Відповіді:


70

Valarrays (масиви значень) призначені для доведення частини швидкості Фортран до C ++. Ви не робите різноманітних покажчиків, щоб компілятор міг робити припущення щодо коду та краще оптимізувати його. (Основна причина того, що Fortran настільки швидкий, це те, що не існує типу вказівника, тому не може бути в'язання вказівника.)

У Valarrays також є класи, які дозволяють нарізати їх досить легко, хоча ця частина стандарту може використовувати трохи більше роботи. Змінення їх розміру є руйнівним і їм не вистачає ітераторів.

Отже, якщо це цифри, з якими ви працюєте, та зручність - це не все, що важливо, використовуйте valarrays. Інакше вектори просто набагато зручніші.


11
Вони не призначені для уникнення покажчиків. C ++ 11 визначає початок () і кінець () у valarray, що повертає їм ітератори
Мохамед Ель-Накіб

3
@ user2023370: тому так багато користувачів Fortran віддають перевагу Fortran 77. :)
Майкл,

152

valarrayє різновидом сироти, який народився в неправильному місці в неправильний час. Це спроба оптимізації, досить конкретно для машин, які використовувались для важкої математики, коли це було написано - конкретно, векторних процесорів, таких як Crays.

Для векторного процесора, що ви, як правило, хотіли зробити, це застосувати одну операцію до всього масиву, потім застосувати наступну операцію до всього масиву і так далі, поки ви не зробите все, що вам потрібно зробити.

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

valarrayпередбачається також усунути будь-яку можливість псевдоніму, що (принаймні теоретично) дозволяє компілятору підвищити швидкість, оскільки вільніше зберігати значення в регістрах. Насправді, однак, я зовсім не впевнений, що будь-яка реальна реалізація в значній мірі використовує це. Я підозрюю, що це скоріше проблема курячих яєць - без підтримки компілятора вона не стала популярною, і поки вона не популярна, ніхто не піде на проблему, працюючи над своїм компілятором, щоб її підтримати.

Існує також дивовижний (буквально) масив допоміжних класів, який можна використовувати з valarray. Ви slice, slice_array, gsliceі gslice_arrayграти з шматками valarray, і зробити його діяти як багатовимірний масив. Ви також можете mask_array"замаскувати" операцію (наприклад, додайте елементи в x до y, але тільки в позиціях, де z не дорівнює нулю). Щоб скористатись більш ніж тривіальним valarray, вам доведеться багато дізнатися про ці допоміжні класи, деякі з яких є досить складними, і жоден з них не здається (принаймні мені) дуже добре зафіксованим.

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

Правка (через вісім років, у 2017 році): Деякі з попередніх принаймні певною мірою застаріли. Наприклад, Intel реалізувала оптимізовану версію valarray для свого компілятора. Для підвищення продуктивності він використовує інтегровані примітивні характеристики Intel (Intel IPP). Хоча точне поліпшення продуктивності, безперечно, різниться, швидкий тест із простим кодом показує приблизно 2: 1 підвищення швидкості в порівнянні з ідентичним кодом, складеним при "стандартній" реалізації valarray.

Тож, хоча я не зовсім впевнений, що програмісти C ++ почнуть використовувати valarrayу величезній кількості, є принаймні деякі обставини, за яких це може забезпечити підвищення швидкості.


1
Чи заборонено спеціально зберігати довільні типи об'єктів всередині valarray?
користувач541686

6
@Mehrdad: Так - у [Numeric.Requirements] є (досить довгий) список обмежень. Всього лише кілька прикладів заборонені всі абстрактні класи та винятки. Він також вимагає еквівалентності між (наприклад) побудовою копії та послідовністю побудови за замовчуванням з подальшим призначенням.
Джеррі Труну

@JerryCoffin sheesh, що страшно. ми обіцяємо, що ми не будемо ним користуватися.
Hani Goc

4
Я б не вирішив це, грунтуючись на страху. Я б вирішив, виходячи з того, чи потрібно зберігати елементи, які використовують функції, заборонені.
Джеррі Труну

3
@annoying_squid: Якщо ви маєте більш конкретну та (ви вважаєте) точну інформацію, яку потрібно додати, будь ласка, не соромтесь додати відповідь, у якій її буде показано. Наразі ваш коментар, схоже, не додає корисної інформації.
Джеррі Труну

39

Під час стандартизації C ++ 98, valarray був розроблений, щоб дозволити якісь швидкі математичні обчислення. Однак приблизно в цей час Тодд Велдхуйзен винайшов шаблони виразів і створив блиц ++ , і були винайдені подібні шаблонні мета-методи, які зробили валярії досить застарілими до виходу стандарту. IIRC, оригінальний пропонувач (і) валаррая відмовився від нього на півдорозі в стандартизацію, що (якщо це правда), йому теж не допомогло.

ISTR полягає в тому, що головна причина, що її не було вилучено зі стандарту, полягає в тому, що ніхто не знайшов часу, щоб ретельно оцінити проблему і написати пропозицію про її видалення.

Будь ласка, майте на увазі, що все це туманно пам’ятається з чуток. Візьміть це з зерном солі і сподівайтеся, що хтось це виправить чи підтвердить.


шаблони виразів можна однаково зараховувати і до Vandevoorde, правда?
Нікос Афанасьоу

@Nikos: Не те, що я знаю. Я можу помилитися. Що ти маєш на користь цього читання?
sbi

1
це згадується в книзі "Шаблони C ++ - повний посібник", я вважаю, що загальновизнано, що вони обидва винайшли їх самостійно .
Нікос Афанасьоу

27

Я знаю, що у валярій є синтаксичний цукор

Я мушу сказати, що я не думаю, std::valarraysщо сильно змінює цукор. Синтаксис інший, але різницю я б не назвав "цукром". API дивний. Розділ на std::valarrays в мові програмування C ++ згадується цей незвичайний API і той факт, що, оскільки std::valarrayочікується, що вони будуть оптимізовані, будь-які повідомлення про помилки, які ви отримуєте під час їх використання, ймовірно, не будуть інтуїтивними.

З цікавості близько року тому я виступав std::valarrayпроти std::vector. У мене більше немає коду або точних результатів (хоча це не повинно бути важко написати власним). З допомогою GCC я зробив отримати трохи виграшу в продуктивності при використанні std::valarrayдля простої математики, але не для моїх реалізацій для обчислення стандартного відхилення (і, звичайно ж , стандартне відхилення не так складний, наскільки це математика йде). Я підозрюю, що операції над кожним елементом у великій std::vectorгрі краще грати з кешами, ніж операції на std::valarrays. ( ПРИМІТКА , дотримуючись поради від musiphil , мені вдалося отримати майже однакові показники від vectorтаvalarray ).

Зрештою, я вирішив використовувати std::vector, приділяючи пильну увагу таким речам, як розподіл пам’яті та створення тимчасових об’єктів.


Обидва std::vectorі std::valarrayзберігають дані в суміжний блок. Однак вони отримують доступ до цих даних, використовуючи різні шаблони, і що ще важливіше, API дляstd::valarray підтримує різні шаблони доступу, ніж API для std::vector.

Для прикладу стандартного відхилення мені на певному кроці потрібно було знайти середнє значення колекції та різницю між значенням кожного елемента та середнім.

Для цього std::valarrayя зробив щось на кшталт:

std::valarray<double> original_values = ... // obviously I put something here
double mean = original_values.sum() / original_values.size();
std::valarray<double> temp(mean, original_values.size());
std::valarray<double> differences_from_mean = original_values - temp;

Я, можливо, був розумнішим з std::sliceабоstd::gslice . Зараз уже більше п’яти років.

Бо std::vectorя щось робив так:

std::vector<double> original_values = ... // obviously, I put something here
double mean = std::accumulate(original_values.begin(), original_values.end(), 0.0) / original_values.size();

std::vector<double> differences_from_mean;
differences_from_mean.reserve(original_values.size());
std::transform(original_values.begin(), original_values.end(), std::back_inserter(differences_from_mean), std::bind1st(std::minus<double>(), mean));

Сьогодні я б точно написав це інакше. Якщо нічого іншого, я би скористався C ++ 11 лямбдами.

Очевидно, що ці два фрагменти коду роблять різні речі. Для одного std::vectorприкладу не робиться проміжна колекція, як у std::valarrayприкладі. Однак я вважаю, що їх справедливо порівняти, оскільки відмінності пов'язані з відмінностями між std::vectorта std::valarray.

Коли я писав цю відповідь, я підозрював, що відняття значення елементів з двох std::valarrays (останній рядок у std::valarrayприкладі) буде менш привабливим для кешу, ніж відповідний рядок у std::vectorприкладі (що трапляється і в останньому рядку).

Виявляється, однак, що

std::valarray<double> original_values = ... // obviously I put something here
double mean = original_values.sum() / original_values.size();
std::valarray<double> differences_from_mean = original_values - mean;

Це те саме, що на std::vectorприкладі, і має майже однакові показники. Зрештою, питання в тому, який API ви віддаєте перевагу.


Я не можу придумати жодної причини, чому a std::vectorграв би краще з кешами, ніж a std::valarray; вони обоє виділяють єдиний суміжний блок пам'яті для своїх елементів.
musiphil

1
@musiphil Моя відповідь була занадто довгою для коментаря, тому я оновив відповідь.
Макс Лібберт

1
У вашому valarrayприкладі вище, вам не довелося конструювати temp valarrayоб’єкт, але ви могли це зробити std::valarray<double> differences_from_mean = original_values - mean;, і тоді кеш-поведінка повинна бути схожою з vectorприкладом. (До речі, якщо meanце насправді int, ні double, вам може знадобитися static_cast<double>(mean).)
musiphil

Дякуємо за пропозицію прибрати valarray. Мені потрібно переглянути, чи покращує це продуктивність. Щодо meanбуття int: це була помилка. Я спочатку написав приклад на використанні ints, а потім зрозумів, що meanтоді буде дуже далеко від реальної середньої через усічення. Але я пропустив кілька необхідних змін у своєму першому раунді редагувань.
Макс Лібберт

@musiphil Ти маєш рацію; ця зміна привела зразок коду до майже однакової продуктивності.
Макс Лібберт

23

valarray повинен був дозволити деякій доброті для обробки векторів FORTRAN на C ++. Якось необхідної підтримки компілятора ніколи насправді не було.

Книги Йозуттіса містять цікавий (дещо зневажливий) коментар до валярі ( тут і тут ).

Однак, здається, Intel зараз переглядає валярій у своїх останніх випусках компілятора (наприклад, див. Слайд 9 ); це цікава подія з огляду на те, що до їхнього 4-стороннього набору інструкцій SIMD SSE збирається приєднатись 8-стрічковими інструкціями AVX та 16-ти напрямками Larrabee, а в інтересах портативності, швидше за все, буде набагато краще кодувати з абстракцією, як валяррей, ніж (скажімо) внутрішні слова.


16

Я знайшов одне хороше використання для valarray. Це використовувати valarray так само, як нумеровані масиви.

auto x = linspace(0, 2 * 3.14, 100);
plot(x, sin(x) + sin(3.f * x) / 3.f + sin(5.f * x) / 5.f);

введіть тут опис зображення

Ми можемо реалізувати вище за допомогою valarray.

valarray<float> linspace(float start, float stop, int size)
{
    valarray<float> v(size);
    for(int i=0; i<size; i++) v[i] = start + i * (stop-start)/size;
    return v;
}

std::valarray<float> arange(float start, float step, float stop)
{
    int size = (stop - start) / step;
    valarray<float> v(size);
    for(int i=0; i<size; i++) v[i] = start + step * i;
    return v;
}

string psstm(string command)
{//return system call output as string
    string s;
    char tmp[1000];
    FILE* f = popen(command.c_str(), "r");
    while(fgets(tmp, sizeof(tmp), f)) s += tmp;
    pclose(f);
    return s;
}

string plot(const valarray<float>& x, const valarray<float>& y)
{
    int sz = x.size();
    assert(sz == y.size());
    int bytes = sz * sizeof(float) * 2;
    const char* name = "plot1";
    int shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);
    ftruncate(shm_fd, bytes);
    float* ptr = (float*)mmap(0, bytes, PROT_WRITE, MAP_SHARED, shm_fd, 0);
    for(int i=0; i<sz; i++) {
        *ptr++ = x[i];
        *ptr++ = y[i];
    }

    string command = "python plot.py ";
    string s = psstm(command + to_string(sz));
    shm_unlink(name);
    return s;
}

Також нам потрібен скрипт python.

import sys, posix_ipc, os, struct
import matplotlib.pyplot as plt

sz = int(sys.argv[1])
f = posix_ipc.SharedMemory("plot1")
x = [0] * sz
y = [0] * sz
for i in range(sz):
    x[i], y[i] = struct.unpack('ff', os.read(f.fd, 8))
os.close(f.fd)
plt.plot(x, y)
plt.show()

2
У мене були буквально такі самі думки, як у вас, коли я дізнався про валярій сьогодні на роботі. Я думаю, відтепер для проблем з математичною обробкою в c ++ я буду використовувати valarray, оскільки код виглядає набагато простіше, ніж у стендах з математичної точки зору.
Захарій Краус

8

Стандарт C ++ 11 говорить:

Класи масивів valarray визначаються як такі, що не містять певних форм псевдоніму, що дозволяє оптимізувати операції з цими класами.

Див. C ++ 11 26.6.1-2.


Оскільки я припускаю, що Стандарт визначає форми, ви можете їх процитувати? Крім того, чи реалізуються вони за допомогою прийомів кодування, або вони є винятками на основі компілятора з правил псевдоніму в інших місцях мови?
підкреслити_29

2

З std::valarrayви можете використовувати стандартні математичні позначення , як v1 = a*v2 + v3з коробки. З векторами це неможливо, якщо ви не визначите власних операторів.


0

std :: valarray призначений для важких числових завдань, таких як Динаміка обчислювальної рідини або Динаміка обчислювальної структури, в яких у вас є масиви з мільйонами, іноді десятками мільйонів предметів, і ви перебираєте їх у циклі з мільйонами часових кроків. Можливо, сьогодні std :: vector має порівнянну продуктивність, але, приблизно, 15 років тому, valarray був майже обов'язковим, якщо ви хотіли написати ефективний числовий вирішувач.

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