простое число менее 2 миллиардов - использование std :: list снижает производительность

Стоит отметить, что вы, возможно, смогли прочитать это прямо, например. если вы использовали read_csv , используя parse_dates=[['Date', 'Time']].

Предполагая, что это просто строки, вы можете просто добавить их вместе (с пробелом), что позволит вам применить to_datetime :

:

In [11]: df['Date'] + ' ' + df['Time']
Out[11]:
0    01-06-2013 23:00:00
1    02-06-2013 01:00:00
2    02-06-2013 21:00:00
3    02-06-2013 22:00:00
4    02-06-2013 23:00:00
5    03-06-2013 01:00:00
6    03-06-2013 21:00:00
7    03-06-2013 22:00:00
8    03-06-2013 23:00:00
9    04-06-2013 01:00:00
dtype: object

In [12]: pd.to_datetime(df['Date'] + ' ' + df['Time'])
Out[12]:
0   2013-01-06 23:00:00
1   2013-02-06 01:00:00
2   2013-02-06 21:00:00
3   2013-02-06 22:00:00
4   2013-02-06 23:00:00
5   2013-03-06 01:00:00
6   2013-03-06 21:00:00
7   2013-03-06 22:00:00
8   2013-03-06 23:00:00
9   2013-04-06 01:00:00
dtype: datetime64[ns]

Примечание: удивительно (для меня) это прекрасно работает, когда NaN преобразуются в NaT, но стоит беспокоиться о том, что преобразование (возможно, использование аргумент raise).

1
задан Will Ness 11 March 2019 в 10:06
поделиться

2 ответа

Первое, что нужно сделать, это убедиться, что вы компилируете с включенной оптимизацией. Классы шаблонов стандартной библиотеки c ++ имеют тенденцию работать очень плохо с неоптимизированным кодом, поскольку они генерируют много вызовов функций. Оптимизатор включает большинство этих вызовов функций, что делает их намного дешевле.

std::list является связанным списком. Это в основном полезно, когда вы хотите вставить или удалить элементы случайным образом (т.е. не с конца).

Для случая, когда вы добавляете только конец списка std::list, возникают следующие проблемы:

  • Итерация по списку является относительно дорогой, так как код должен следовать указателям на узлы и затем получить данные
  • В списке используется гораздо больше памяти, каждому элементу требуется указатель на предыдущий и следующий узлы в дополнение к фактическим данным. В 64-битной системе это равно 20 байтов на элемент, а не 4 для списка int
  • Поскольку элементы в списке не являются смежными в памяти, компилятор не может выполнить столько SIMD-оптимизаций и вы будете больше страдать от ошибок кэша ЦП

A std::vector решит все вышеперечисленное, так как его память является смежной, и итерации по ней - просто случай увеличения индекса массива. Вам нужно убедиться, что вы вызываете reserve для вашего вектора в начале с достаточно большим значением, чтобы при добавлении к вектору весь массив не копировался в новый больший массив.

Лучшая оптимизация, чем приведенная выше, - использовать Сито Эратосфена для вычисления ваших простых чисел. Поскольку генерация этого света требует случайного удаления (в зависимости от вашей точной реализации), std::list может работать лучше, чем std::vector, хотя даже в этом случае издержки std::list могут не перевесить его стоимость.

0
ответ дан Alan Birtles 11 March 2019 в 10:06
поделиться

Причина, по которой ваш второй пример занимает больше времени, заключается в том, что вы выполняете std::list.

std::list в C ++ - это связанный список, что означает, что он не использует непрерывную память. Это плохо, потому что для итерации списка вы должны переходить от узла к узлу непредсказуемым образом (к процессору / сборщику). Кроме того, вы, скорее всего, «используете» только несколько байтов каждой кеш-линии. Оперативная память медленная. Извлечение байта из ОЗУ занимает много лота дольше, чем извлечение его из L1. В наши дни процессоры работают быстро, поэтому ваша программа большую часть времени ничего не делает и ожидает поступления памяти.

Вместо этого используйте std::vector. Он хранит все значения один за другим, и итерация очень дешевая. Поскольку вы выполняете итерацию вперед в памяти без скачков, вы используете полную кеш-строку, и ваш модуль предварительной выборки сможет получать дополнительные страницы, прежде чем они понадобятся, потому что ваш доступ к памяти предсказуем.

Многие люди, в том числе Бьярн Страуструп, доказали, что std::vector во многих случаях быстрее, чем std::list, даже в тех случаях, когда std::list имеет «теоретически» лучшую сложность (случайная вставка, удаление ...) только потому, что кеширование очень помогает. Поэтому всегда используйте std::vector по умолчанию. И если вы думаете, связанный список будет быстрее в вашем случае, измерьте его и удивитесь, что - большую часть времени - std::vector доминирует.

Редактировать: как уже отмечали другие, ваш метод поиска простых чисел не очень эффективен. Я просто немного поиграл и реализовал Сито Эратосфена , используя набор битов.

constexpr int max_prime = 1000000000;
std::bitset<max_prime> *bitset = new std::bitset<max_prime>{};
// Note: Bit SET means NO prime
bitset->set(0);
bitset->set(1);

for(int i = 4; i < max_prime ; i += 2)
    bitset->set(i); // set all even numbers
int max = sqrt(max_prime);
for(int i = 3; i < max; i += 2) { // No point testing even numbers as they can't be prime
    if(!bitset->test(i)) { // If i is prime
        for(int j = i * 2; j < no_primes; j += i)
            bitset->set(j); // set all multiples of i to non-prime
    }
}

Это займет от 4,2 до 4,5 секунд 30 секунд (не уверен, почему он изменился так сильно после небольших модификаций ... должна быть оптимизация, которую я больше не выполняю), чтобы найти все простые числа ниже одного миллиарда (1 000 000 000) на моей машине. Ваш подход занял слишком много времени даже для 100 миллионов. Я отменил поиск в 1 миллиард примерно через две минуты.

Сравнение для 100 миллионов:

time taken:                63.515 seconds
time taken bitset:         1.874 seconds
No of divisions :          1975961174
No of primes found:        5761455
No of primes found bitset: 5761455

Я не математик, поэтому я уверен, что есть еще способы оптимизировать его, я оптимизирую только для четных чисел.

0
ответ дан tkausl 11 March 2019 в 10:06
поделиться
Другие вопросы по тегам:

Похожие вопросы: