Обложка статьи «Сравнение скорости Python и C++»

Сравнение скорости Python и C++

Пример для дата-сайентистов, которые считают, что им не нужно учить C++

Адаптированный перевод статьи How fast is C++ compared to Python?

Прим. ред. Это перевод статьи Назера Тамими. Мнение редакции может не совпадать с мнением автора оригинала.

Есть миллион причин любить Python (особенно если вы дата-сайентист).  Но насколько Python отличается от низкоуровневых языков, таких как Си и C++? В этой статье я собираюсь сделать сравнение скорости Python и C++, на очень простом примере.

Мы будем генерировать все возможные k-меры ДНК, для фиксированного
значения «k». О том, что такое k-меры, я расскажу чуть позже. Этот пример был выбран потому, что многие задачи обработки и анализа данных связанные с геномом, считаются ресурсоёмкими. Поэтому, многие дата-сайентисты связанные с биоинформатикой, интересуются C++ (в дополнение к Python).

Важное замечание: цель этой статьи не сравнить скорость С++ и Python когда они наиболее эффективны. Код предлагаемых программ можно сделать гораздо более быстрым. Цель этой статьи — сравнить два языка, используя один и тот же алгоритм и код.

Введение в k-меры ДНК

ДНК — это длинная цепь нуклеотидов. Эти нуклеотиды могут быть четырёх типов: A, C, G и T. У вида Homo Sapiens около 3 миллиардов пар нуклеотидов. Вот небольшой кусок ДНК человека:

ACTAGGGATCATGAAGATAATGTTGGTGTTTGTATGGTTTTCAGACAATT

Чтобы получить из него k-меры нужно разбить строку на части:

ACTA, CTAG, TAGG, AGGG, GGGA и т. д.

Эти последовательности из четырех символов называются k-меры длина которых равна четырём (4-меры).

Задача

Мы сгенерируем всё возможные 13-меры. Математически — это перестановка с проблемой замены. Следовательно мы имеем 4 в степени 13 (67 108 864) вариантов 13-меров.

Сравнение скорости Python и С++

Мы будем использовать один и тот же алгоритм для двух языков. Код на обоих языках намеренно написан аналогично и просто. Я не использовал сложные структуры данных и сторонние библиотеки. Вот код программы на Python:

def convert(c):
    if (c == 'A'): return 'C'
    if (c == 'C'): return 'G'
    if (c == 'G'): return 'T'
    if (c == 'T'): return 'A'

print("Start")

opt = "ACGT"
s = ""
s_last = ""
len_str = 13

for i in range(len_str):
    s += opt[0]

for i in range(len_str):
    s_last += opt[-1]

pos = 0
counter = 1
while (s != s_last):
    counter += 1
    # Следующая строка выводит результаты
    # print(s)
    change_next = True
    for i in range(len_str):
        if (change_next):
            if (s[i] == opt[-1]):
                s = s[:i] + convert(s[i]) + s[i+1:]
                change_next = True
            else:
                s = s[:i] + convert(s[i]) + s[i+1:]
                break

# Следующая строка выводит результаты
# print(s)
print("Number of generated k-mers: {}".format(counter))
print("Finish!")

Выполнение этой программы займет 61.23 секунды. За это время сгенерируется 67 миллионов 13-меров. Чтобы не увеличивать время работы программы я закомментировал код выводящий результаты (25 и 37 строки). Если вы захотите запустить этот код и отобразить результаты, имейте ввиду, что это будет очень долго. Чтобы остановить выполнение программы вы можете нажать на клавиатуре CTRL+С.

Теперь посмотрим тот же алгоритм на языке C++:

#include 
#include 

using namespace std;

char convert(char c)
{
    if (c == 'A') return 'C';
    if (c == 'C') return 'G';
    if (c == 'G') return 'T';
    if (c == 'T') return 'A';
    return ' ';
}

int main()
{
    cout << "Start" << endl; 

    string opt = "ACGT";
    string s = "";
    string s_last = "";
    int len_str = 13;
    bool change_next;

    for (int i=0; i<len_str; i++)
    {
        s += opt[0];
    }

    for (int i=0; i<len_str; i++)
    {
        s_last += opt.back();
    }

    int pos = 0;
    int counter = 1;
    while (s != s_last)
    {   
        counter ++;
        // Следующая строка выводит результаты // cout << s << endl; change_next = true; for (int i=0; i<len_str; i++) { if (change_next) { if (s[i] == opt.back()) { s[i] = convert(s[i]); change_next = true; } else { s[i] = convert(s[i]); break; } } } } // Следующая строка выводит результаты // cout << s << endl; cout << "Number of generated k-mers: " << counter << endl; cout << "Finish!" << endl; return 0; }
таблца результатов сравнения скорости Python и C++

В таблице указаны результаты тестов для 13, 14, и 15-меров.

После компиляции, этот код выполнится за 2.42 секунды. Получается что Python требуется в 25 раз больше времени на эту задачу. Я повторил эксперимент с 14 и 15-мерами (это можно указать на 12 строке в Python и на 22 в C++) Теперь мы видим, что производительность этих двух языков, при выполнении одной и той же задачи, значительно различается.

Я повторюсь, обе программы далеки от идеала и могут быть значительно опимизированы. Например, мы не использовали параллельные вычисления на CPU или GPU. Но для таких задач это необходимо. Также мы не храним результаты. Хотя управление памятью в Python и C++ значительно влияет на производительность.

Этот пример и тысячи других задач, подтверждают, что дата-сайентистам стоит обращать внимание на C++ и подобные ему языки, когда нужно работать с большими массивами данных или требующими большой производительности процессами.