Вы когда-нибудь задумывались, что может случиться, если вы напишете один и тот же алгоритм на двух разных языках программирования? Хорошо, я сделал.

Совершенно очевидно, что нет двух одинаковых языков. Если все языки были клонами друг друга, тогда зачем изобретать другой язык, верно? Но насколько разными могут быть два языка? Давайте посмотрим на язык программирования Python и C, который, как известно, является одним из самых быстрых языков. Прежде чем мы это сделаем, мы должны немного пересмотреть, в чем могут быть различия.

Языковые различия

Мы можем взглянуть на эти различия с точки зрения программиста и с технической точки зрения. Конечно, я уверен, что вы найдете множество классификаций языков программирования в литературе, но пока оставим их академикам.

Точка зрения программистов

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

  • Читаемость (Легко ли читать язык? Легко ли следовать логике?)
  • Простота (обучения, использования,…)
  • Симпатичность (Как нам нравится использовать язык)
  • Скорость программирования (Сколько времени нужно на создание определенного функционала?)

Техническая точка зрения

Все различия здесь зависят от того, как язык был разработан и для каких целей. Вот отличия, которые нам будут интересны в этой истории. Некоторые из них:

  • Парадигма программирования (процедурное, объектно-ориентированное, функциональное и т. Д.)
  • Аппаратный уровень (низкий уровень, высокий уровень)
  • Цель (система, сценарий, для домена,…)
  • Переводчик языков (компилятор, интерпретатор или гибрид)

Python

Этот язык был впервые выпущен в 1991 году. Первоначально он был разработан как язык сценариев, но сегодня он может обрабатывать практически все. Это полноценный язык программирования высокого уровня, использующий в качестве языкового переводчика интерпретатор. Что касается парадигмы языка, Python включает императивную (процедурную), объектно-ориентированную и даже функциональную парадигмы.

C

Язык был разработан в 1972 году, в основном как системный язык. Даже сегодня он работает в большинстве операционных систем. Помимо целей системного языка, он также часто используется в задачах, критичных к безопасности и производительности. В зависимости от используемой литературы он классифицируется как язык низкого или высокого уровня. Язык переводится через компилятор. C использует императивную (процедурную) парадигму.

Наивный алгоритм грубой силы

Следующему алгоритму во всех его вариантах предстоит выполнить одно задание - угадать присвоенный ему PIN-код. Я называю это наивным алгоритмом, потому что алгоритм был дан в виде простого текста, что такое ПИН-код и, следовательно, его длина. Все, что ему нужно сделать, это пройти все комбинации (с повторениями) в заданном пространстве комбинаций и по достижении правильной комбинации вернуть true. В реальной жизни ваш алгоритм не получит PIN-код / ​​пароль в виде обычного текста. Скорее всего, вы получите его в хешированной или зашифрованной форме.

Независимо от формы, есть важный факт, что выполнение всех комбинаций требует больших вычислительных ресурсов. Мы можем разумно измерить время выполнения и проанализировать результаты.

Объяснение алгоритма

Как я уже упоминал выше, он должен пройти все комбинации. Алгоритм создает комбинации точно так же, как вы пишете комбинации на бумаге. Допустим, мы хотим получить все комбинации с повторениями длины 3 с использованием цифр [0, 1, 2]. Как человек, вы, вероятно, будете выполнять комбинации в таком порядке (следуйте столбцам):

000  010  020 | 100  110  120 | 200  210  220
001  011  021 | 101  111  121 | 201  211  221
002  012  022 | 102  112  122 | 202  212  222

Таким же образом алгоритм создает комбинации. Теперь давайте познакомимся с кодом Python и C.

Код Python

Вы также можете найти исходные коды вариантов Python в моем репозитории GitHub. Также имеется код для синхронизации функций.

Вариант строки

Этот вариант никоим образом не изменит данный PIN-код. Он будет генерировать попытки угадать пароль в виде строковых объектов.

def pin_code_str(pin_code):
    last_index = len(pin_code) - 1
    digits = [char for char in "1234567899"]
    attempt = len(pin_code) * "0"

    while True:
        for digit in digits:
            if attempt == pin_code:
                return True

            attempt = attempt[:last_index] + digit

        nine_count = 0
        index = last_index
        while attempt[index] == "9":
            nine_count += 1
            index -= 1

        attempt = attempt[:index] + chr(ord(attempt[index]) + 1) + (nine_count * digits[0])

Целочисленный вариант

Этот вариант преобразует PIN-код в список его цифр, преобразованных в int. Он будет генерировать попытки угадать пароль путем изменения цифр в необходимых индексах списка попыток.

def pin_code_int(pin_code):
    last_index = len(pin_code) - 1
    pin_code = [int(x) for x in pin_code.strip()]
    digits = [x for x in range(1, 11)]
    attempt = [0 for _ in range(len(pin_code))]

    while True:
        for digit in digits:
            if attempt == pin_code:
                return True

            attempt[last_index] = digit

        for index in range(last_index, -1, -1):
            if attempt[index] < 9:
                attempt[index] += 1
                break

            attempt[index] = 0

Использование Itertools

Все вышеперечисленные варианты запрограммированы без использования импорта. Помимо корректности алгоритма, нас может заинтересовать скорость, не так ли? В Python есть несколько библиотек, которые поддерживаются языком программирования C, поэтому мы можем использовать их для ускорения кода.

Функция itertools.product

Функция product из библиотеки itertools создаст итератор, который мы можем использовать для перебора всех комбинаций. Для нас важно то, что он создает все комбинации в том же порядке, что и код выше. Каждая комбинация дается нам как кортеж, состоящий из цифр. Каждая цифра в кортеже будет иметь один и тот же тип данных, в зависимости от типа данных, который вы передаете функции. В результате, если мы хотим сравнить комбинацию с PIN-кодом, который мы хотим угадать, мы должны выполнить преобразования в обоих вариантах.

Вариант строки Itertools

В этом случае мы должны преобразовать данный PIN-код в набор цифр в виде строк. Элементы, предоставленные функции product, будут сохранены в списке строк, где каждая строка представляет собой одну цифру. Наконец, итератор будет удерживаться в переменной plays.

def pin_code_itertools_str(pin_code):
    pin_code = tuple(pin_code)
    digits = [digit for digit in "0123456789"]
    attempts = product(digits, repeat=len(pin_code))
    return pin_code in attempts

Itertools Int вариант

Похожий подход, но на этот раз мы работаем с попытками угадать как кортежи целых чисел. Это означает, что мы также должны предоставить элементы функции продукта в виде списка целых чисел.

def pin_code_itertools_int(pin_code):
    pin_code = tuple([int(digit) for digit in pin_code])
    digits = [x for x in range(10)]
    attempts = product(digits, repeat=len(pin_code))
    return pin_code in attempts

Код C

Следующий код C также работает так же, как и в Python. Это означает, что он будет проходить все комбинации в одном и том же порядке. Это код Python без библиотеки itertools, переписанный на язык программирования C. Конечно, язык программирования C работает на совершенно другом уровне, чем Python, и мы обсудим различия после тестов. Вы можете найти полный исходный код с функцией тайминга в моем репозитории GitHub.

int pin_brute(char* pin_code) {
    unsigned last_index = strlen(pin_code) - 1;
    char *attempt = calloc(last_index + 1, sizeof(char));
    memset(attempt, '0', last_index + 1);
    while (1) {
        for (unsigned i = 0; i <= 9; i++) {
            if (strcmp(attempt, pin_code) == 0) {
                free(attempt);
                return 0;
            }
            attempt[last_index]++;
        }
        attempt[last_index] = '9';
        for (unsigned j = last_index; j >= 0; j--) {
            if (attempt[j] == '9') {
                attempt[j] = '0';
                continue;
            }
            attempt[j]++;
            break;
        }
    }
}

Результаты тестов

ПРИМЕЧАНИЕ. Результаты сравнения представляют собой среднее время выполнения из 10 запусков. PIN код для грубой силы был намеренно 9999999, чтобы алгоритм перебрал все комбинации в заданном пространстве комбинаций.

PIN code: 9999999 -- Length: 7
Function: pin_code_str
Average execution time: 1.431s
Function: pin_code_int
Average execution time: 1.363s
Function: pin_code_itertools_str
Average execution time: 0.404s
Function: pin_code_itertools_int
Average execution time: 0.357
Function: pin_brute
Average execution time: 0.157s

ПРИМЕЧАНИЕ: использовался ЦП Intel Core i7–8750H. В зависимости от вашего процессора время будет другим.

Как видите, используя один и тот же алгоритм, но на разных языках программирования, можно ожидать разных результатов. Я намеренно выбрал Python, чтобы конкурировать с языком программирования C. Несомненно, язык C оправдывает свою репутацию одного из самых быстрых языков.

Давайте посмотрим на графическое представление наших результатов для функций Python:

Вам нечего терять, если вы попытаетесь поэкспериментировать со своим кодом, попробовать другой подход или найти подходящую библиотеку, которая делает именно то, что вы хотите. Очень удобно иметь увеличение скорости на 300% по сравнению с первоначальной реализацией. Но что, если мы посчитаем в реализации кода C?

Реализация кода C предлагает значительное увеличение скорости на 811% по сравнению с исходной реализацией Python. Только что произошло то, что код C обработал 10 миллионов комбинаций всего за 0,157 секунды, будучи в 9,11 раз быстрее, чем первоначальная реализация.

В чем причины такой огромной разницы?

  • Язык C ближе к аппаратному уровню и транслируется через компилятор. В общем, скомпилированные языки быстрее интерпретируемых языков.
  • C - это статически типизированный язык, а это означает, что все, что может иметь назначенный ему тип данных, должно иметь объявленный тип данных. C выполняет все проверки перед запуском кода. Python - это язык с динамической типизацией, что снижает время выполнения, поскольку интерпретатор Python должен разбираться во время выполнения.
  • В языке C вы должны указать компилятору, где находятся переменные в памяти (указатели). Вы должны сами создать место в памяти для своей переменной. После того, как вы закончите, вы должны убрать за собой. Если у вас заканчивается память, вам необходимо перераспределить память в сектор с большим объемом. Вы - мастер памяти, что позволяет вам лучше оптимизировать. В Python все это делается за вас благодаря абстракции от реальных принципов работы оборудования и того, как работают компьютеры. Эта абстракция также заставляет Python работать медленнее.
  • Разница в строковых представлениях. В Python, когда мы хотели создать новую комбинацию, Python сделал за нас несколько шагов. Строковые объекты в Python неизменяемы. Это означает, что вы не можете изменить значение по заданному индексу строки (python_string [index] = new_character # Это не будет работать в Python). Если вы хотите это сделать, вы должны использовать нарезку и конкатенацию. Python скопирует старое строковое значение, сотрет старый строковый объект, а затем создаст новый строковый объект, имеющий соответствующие старые строковые значения с объединенным новым значением. В C все, что вы делаете, это напрямую меняете биты в памяти, чтобы они представляли нужный вам символ.

Итак, почему мы используем Python?

Короче говоря, как я уже упоминал, Python намного превосходит аппаратный уровень, что упрощает воплощение ваших идей в код. Он более удобный, вы можете выучить его намного быстрее и делать разные вещи. Вероятно, самая важная причина - это стоимость программирования на Python. Разработка на Python идет довольно быстро по сравнению с языком программирования C. Время - деньги, а Python сэкономит вам время и деньги.

Заключение

Прежде чем вы даже начнете кодировать, вы должны сначала подумать о языке, который вы хотите использовать. Личные проекты могут не иметь большого значения, но если вы настроены серьезно, то тщательное изучение ваших возможностей и пригодности языков для проекта может окупиться. Скорость - не единственный критерий. Некоторые языки более естественны для решения определенных задач, чем другие языки. Другие языки могут иметь лучшую поддержку с точки зрения поддерживаемых библиотек, поддерживаемых платформ и т. Д. Выбирайте с умом!