Почему вычисление sha-256 в python быстрее чем в c#?

Я играюсь с двумя языками и решил сравнить скорость вычисления. Написал маленькие программы практически идентичные которые ищут хеш sha-256 с нулями впереди. И почему-то python выходит быстрее и находит длинною 5 нулей за 2.8 секунды, а c# за 6.8? Строки из которых вычисляться хеш и соответственно полученные хеши одинаковы. Я задаю этот вопрос потому что не вижу логики, почему интерпретируемый язык в данных вычислениях в 2,5 раза быстрее и мне интересна причина. Скрипты запускаю через .bat файл один за другим - почти одновременно.

Вот коды:

C#

using System;
using System.IO;
using System.Text;
using System.Diagnostics;
using System.Security.Cryptography;

namespace HelloWorld
{
    class Hello {         
        static void Main(string[] args)
        {
            Stopwatch timer = new Stopwatch();
            
            Console.WriteLine("Hello!");

            int len = 0;
            if (args.Length > 0 && args[0] != null)
            {
                len = Convert.ToInt32(args[0]);
            }
            else
            {
                Console.Write("Mask length: ");
            
                len = Convert.ToInt32(Console.ReadLine());
            }

            string mask = "";

            for (int i = 0; i < len; i++)
            {
                mask += "0";
            }

            Console.WriteLine("Mask: " + mask);
            
            
            
            string str = "Num #";
            string hash = "";

            Console.WriteLine("Search...");
            
            timer.Start();
            
            for (int num = 0; true; num++)
            {
                hash = sha256(str + num);

                if (hash.IndexOf(mask) == 0)
                {
                    str = str + num;
                    break;
                }
            }


            
            Console.WriteLine("Find!");
            Console.WriteLine("Data: " + str);
            Console.WriteLine("Hash: " + hash);
            Console.WriteLine("Done: " + timer.Elapsed);
            
            Console.ReadKey();
        }
        
        
        static string sha256(string randomString)
        {
            var crypt = new System.Security.Cryptography.SHA256Managed();
            var hash = new System.Text.StringBuilder();
            byte[] crypto = crypt.ComputeHash(Encoding.UTF8.GetBytes(randomString));
            foreach (byte theByte in crypto)
            {
                hash.Append(theByte.ToString("x2"));
            }
            return hash.ToString();
        }
    }
}

Python

import sys
import time
import hashlib



def time_convert(sec):
    mins = sec // 60
    sec = sec % 60
    hours = mins // 60
    mins = mins % 60
    return "{0}:{1}:{2}".format(int(hours),int(mins),sec)



print("Hello!");

if (len(sys.argv) > 1):
    len = int(sys.argv[1]);

else:
    len = int(input("Mask len: "));

mask = ""


for i in range(len):
    mask += "0"

print("Mask: ", mask)



word = "Num #"
hash = ""

print("Search...")
start_time = time.time()

num = 0
while(True):
    hash = hashlib.sha256((word + str(num)).encode()).hexdigest()

    if hash.find(mask) == 0:
        word = word + str(num)
        break
      
    num += 1



end_time = time.time()
time_lapsed = end_time - start_time
time_lapsed = time_convert(time_lapsed)

print("Find!");
print("Data: ", word);
print("Hash: ", hash);
print("Done: ", time_lapsed);

input()

Ответы (1 шт):

Автор решения: aepot

Не убегая глубоко в оптимизации. Запускаю ваш код в дебаг сборке.

Hello!
Mask length: 5
Mask: 00000
Search...
Find!
Data: Num #646714
Hash: 00000b4eecf7c2687378eb776f9d7e02f4ee4d46883ec750fdd2e238702ee386
Done: 00:00:09.9405143

Немного оптимизирую.

static void Main(string[] args)
{
    Stopwatch timer = new Stopwatch();

    Console.WriteLine("Hello!");

    int len;
    if (args.Length > 0 && args[0] != null)
    {
        len = int.Parse(args[0]);
    }
    else
    {
        Console.Write("Mask length: ");

        len = int.Parse(Console.ReadLine());
    }

    string mask = new string('0', len);

    Console.WriteLine("Mask: " + mask);


    string str = "Num #";
    string hash;

    Console.WriteLine("Search...");

    timer.Start();

    for (int num = 0; true; num++)
    {
        hash = sha256(str + num);

        if (hash.StartsWith(mask, StringComparison.Ordinal))
        {
            str += num;
            break;
        }
    }

    timer.Stop();

    Console.WriteLine("Find!");
    Console.WriteLine("Data: " + str);
    Console.WriteLine("Hash: " + hash.ToLower());
    Console.WriteLine("Done: " + timer.Elapsed);

    Console.ReadKey();
}

static SHA256Managed crypt = new SHA256Managed();
static string sha256(string randomString)
{
    byte[] crypto = crypt.ComputeHash(Encoding.UTF8.GetBytes(randomString));
    return Convert.ToHexString(crypto);
}

Запускаю дебаг сборку.

Hello!
Mask length: 5
Mask: 00000
Search...
Find!
Data: Num #646714
Hash: 00000b4eecf7c2687378eb776f9d7e02f4ee4d46883ec750fdd2e238702ee386
Done: 00:00:00.8002694

Запускаю релиз сборку.

Hello!
Mask length: 5
Mask: 00000
Search...
Find!
Data: Num #646714
Hash: 00000b4eecf7c2687378eb776f9d7e02f4ee4d46883ec750fdd2e238702ee386
Done: 00:00:00.5345979

То есть грубо говоря в 10 раз быстрее стало. И это еще не предел оптимизации.

Не устану говорить: это не C# медленный, а код, который на нем так написан.

Узкое место было в этой строчке hash.IndexOf(mask) == 0. Сравнение строк выполняется не побайтово, а с учетом культуры, активной в данный момент. Чтобы сравнение было быстрым и побайтовым, надо так hash.IndexOf(mask, StringComparison.Ordinal) == 0. Ну или как я выше в коде написал.

Вынос создания класса хэша из цикла хоть и ускоряет процесс, но менее значительно. Convert.ToHexString тоже чисто для сокращения кода.

По-приколу 6 нулей поискал.

Hello!
Mask length: 6
Mask: 000000
Search...
Find!
Data: Num #31184147
Hash: 000000de3652ae021f32fc4cb4b6fda5807ed3b0fbea4641841a8078b5e4f668
Done: 00:00:24.4929515

Немного поигрался с черной магией, то есть спанами, уложился с 6 нулями в 10 секунд, но показывать не буду, потому что для нечетного количества нулей работать не будет, точнее надо допиливать, а это не соответствует условию задачи. То есть если точность маски нужна 8 бит, а не 4, то можно вообще очень быстро сделать. А знать бы вообще для чего это (крипта наверное) - еще быстрее. Ах да, забыл, еще же многопоточку можно прикрутить. Для моего проца - это x8 к скорости или около того.

→ Ссылка