Мені дуже подобаються вектори. Вони витончені і швидкі. Але я знаю, що ця річ під назвою валярій існує. Чому я використовую валярій замість вектора? Я знаю, що у валярій є синтаксичний цукор, але крім них, коли вони корисні?
Мені дуже подобаються вектори. Вони витончені і швидкі. Але я знаю, що ця річ під назвою валярій існує. Чому я використовую валярій замість вектора? Я знаю, що у валярій є синтаксичний цукор, але крім них, коли вони корисні?
Відповіді:
Valarrays (масиви значень) призначені для доведення частини швидкості Фортран до C ++. Ви не робите різноманітних покажчиків, щоб компілятор міг робити припущення щодо коду та краще оптимізувати його. (Основна причина того, що Fortran настільки швидкий, це те, що не існує типу вказівника, тому не може бути в'язання вказівника.)
У Valarrays також є класи, які дозволяють нарізати їх досить легко, хоча ця частина стандарту може використовувати трохи більше роботи. Змінення їх розміру є руйнівним і їм не вистачає ітераторів.
Отже, якщо це цифри, з якими ви працюєте, та зручність - це не все, що важливо, використовуйте valarrays. Інакше вектори просто набагато зручніші.
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
у величезній кількості, є принаймні деякі обставини, за яких це може забезпечити підвищення швидкості.
Під час стандартизації C ++ 98, valarray був розроблений, щоб дозволити якісь швидкі математичні обчислення. Однак приблизно в цей час Тодд Велдхуйзен винайшов шаблони виразів і створив блиц ++ , і були винайдені подібні шаблонні мета-методи, які зробили валярії досить застарілими до виходу стандарту. IIRC, оригінальний пропонувач (і) валаррая відмовився від нього на півдорозі в стандартизацію, що (якщо це правда), йому теж не допомогло.
ISTR полягає в тому, що головна причина, що її не було вилучено зі стандарту, полягає в тому, що ніхто не знайшов часу, щоб ретельно оцінити проблему і написати пропозицію про її видалення.
Будь ласка, майте на увазі, що все це туманно пам’ятається з чуток. Візьміть це з зерном солі і сподівайтеся, що хтось це виправить чи підтвердить.
Я знаю, що у валярій є синтаксичний цукор
Я мушу сказати, що я не думаю, std::valarrays
що сильно змінює цукор. Синтаксис інший, але різницю я б не назвав "цукром". API дивний. Розділ на std::valarray
s в мові програмування C ++ згадується цей незвичайний API і той факт, що, оскільки std::valarray
очікується, що вони будуть оптимізовані, будь-які повідомлення про помилки, які ви отримуєте під час їх використання, ймовірно, не будуть інтуїтивними.
З цікавості близько року тому я виступав std::valarray
проти std::vector
. У мене більше немає коду або точних результатів (хоча це не повинно бути важко написати власним). З допомогою GCC я зробив отримати трохи виграшу в продуктивності при використанні std::valarray
для простої математики, але не для моїх реалізацій для обчислення стандартного відхилення (і, звичайно ж , стандартне відхилення не так складний, наскільки це математика йде). Я підозрюю, що операції над кожним елементом у великій ( ПРИМІТКА , дотримуючись поради від musiphil , мені вдалося отримати майже однакові показники від std::vector
грі краще грати з кешами, ніж операції на std::valarray
s. 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::valarray
s (останній рядок у 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 ви віддаєте перевагу.
std::vector
грав би краще з кешами, ніж a std::valarray
; вони обоє виділяють єдиний суміжний блок пам'яті для своїх елементів.
valarray
прикладі вище, вам не довелося конструювати temp
valarray
об’єкт, але ви могли це зробити std::valarray<double> differences_from_mean = original_values - mean;
, і тоді кеш-поведінка повинна бути схожою з vector
прикладом. (До речі, якщо mean
це насправді int
, ні double
, вам може знадобитися static_cast<double>(mean)
.)
valarray
. Мені потрібно переглянути, чи покращує це продуктивність. Щодо mean
буття int
: це була помилка. Я спочатку написав приклад на використанні int
s, а потім зрозумів, що mean
тоді буде дуже далеко від реальної середньої через усічення. Але я пропустив кілька необхідних змін у своєму першому раунді редагувань.
valarray повинен був дозволити деякій доброті для обробки векторів FORTRAN на C ++. Якось необхідної підтримки компілятора ніколи насправді не було.
Книги Йозуттіса містять цікавий (дещо зневажливий) коментар до валярі ( тут і тут ).
Однак, здається, Intel зараз переглядає валярій у своїх останніх випусках компілятора (наприклад, див. Слайд 9 ); це цікава подія з огляду на те, що до їхнього 4-стороннього набору інструкцій SIMD SSE збирається приєднатись 8-стрічковими інструкціями AVX та 16-ти напрямками Larrabee, а в інтересах портативності, швидше за все, буде набагато краще кодувати з абстракцією, як валяррей, ніж (скажімо) внутрішні слова.
Я знайшов одне хороше використання для 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()
Стандарт C ++ 11 говорить:
Класи масивів valarray визначаються як такі, що не містять певних форм псевдоніму, що дозволяє оптимізувати операції з цими класами.
Див. C ++ 11 26.6.1-2.
std :: valarray призначений для важких числових завдань, таких як Динаміка обчислювальної рідини або Динаміка обчислювальної структури, в яких у вас є масиви з мільйонами, іноді десятками мільйонів предметів, і ви перебираєте їх у циклі з мільйонами часових кроків. Можливо, сьогодні std :: vector має порівнянну продуктивність, але, приблизно, 15 років тому, valarray був майже обов'язковим, якщо ви хотіли написати ефективний числовий вирішувач.