Подтвердить что ты не робот

Ускоренная оценка рук в покере

Я пытаюсь использовать подход оценки "RayW hand оценщик", чтобы получить оценку комбинации карт (5 лучших карт из 7). Однако с этим методом возникают проблемы с производительностью . Согласно источникам, используя этот подход, можно оценить более 300 мил в секунду! Мой результат составляет 10 мельниц за 1,5 секунды, что во много раз медленнее.

Идея "оценщика рук RayW" следующая:

Оценщик Two Plus Two состоит из большой таблицы поиска, содержащей около тридцати двух миллионов записей (точнее, 32 487 834). В порядке для поиска данной 7-карточной покерной руки, вы прослеживаете путь через этот стол, выполняющий один поиск на карту. Когда вы доберетесь до последней карты, полученное значение является официальным значением эквивалентности руки

вот как выглядит код:

namespace eval
{
public struct TPTEvaluator
{
    public static int[] _lut;

    public static unsafe void Init() // to load a table
    {
        _lut = new int[32487834];
        FileInfo lutFileInfo = new FileInfo("HandRanks.dat");
        if (!lutFileInfo.Exists)
        {throw new Exception("Handranks.dat not found");}

        FileStream lutFile = new FileStream("HandRanks.dat", FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096);

        byte[] tempBuffer = new byte[32487834 * 4];
        lutFile.Read(tempBuffer, 0, 32487834 * 4);

        fixed (int* pLut = _lut)
        { Marshal.Copy(tempBuffer, 0, (IntPtr)pLut, 32487834 * 4);}
        tempBuffer = null;
    }

    public unsafe static int LookupHand(int[] cards) // to get a hand strength
    {
        fixed (int* pLut = _lut)
        {
            int p = pLut[53 + cards[0]];
            p = pLut[p + cards[1]];
            p = pLut[p + cards[2]];
            p = pLut[p + cards[3]];
            p = pLut[p + cards[4]];
            p = pLut[p + cards[5]];
            return pLut[p + cards[6]];
        }
    }
}

}

и что я тестирую этот подход:

    private void button4_Click(object sender, EventArgs e)
    {
        int[] str = new int[] { 52, 34, 25, 18, 1, 37, 22 };

        int r1 = 0;

        DateTime now = DateTime.Now;
        for (int i = 0; i < 10000000; i++) // 10 mil iterations 1.5 - 2 sec
        { r1 = TPTEvaluator.LookupHand(str);} // here
        TimeSpan s1 = DateTime.Now - now;
        textBox14.Text = "" + s1.TotalMilliseconds;
    }

Я считаю, что этот метод был первоначально реализован на С++, но, тем не менее, порт С# должен работать быстрее. Есть ли способ, каким образом я могу приблизиться хотя бы к 100 миллионам рук за одну секунду?

То, что я пробовал до сих пор:

  • пытался использовать статические и нестатические методы - без разницы.
  • попытался использовать поиск словаря вместо массива

    public void ArrToDict(int[] arr, Dictionary<int, int> dic)
    {
        for (int i = 0; i < arr.Length; i++)
        {
            dic.Add(i, arr[i]);
        }
    }
    
    public unsafe static int LookupHandDict(int[] cards)
    {
        int p = dict[53 + cards[0]];
        p = dict[p + cards[1]];
        p = dict[p + cards[2]];
        p = dict[p + cards[3]];
        p = dict[p + cards[4]];
        p = dict[p + cards[5]];
        return dict[p + cards[6]];
    }
    

Истекшее время для 10 мельниц рук почти в 6 раз медленнее.

  • По словам одного человека - он увеличил производительность на 200 мельниц, удалив "небезопасный" код. Я попытался сделать то же самое, но результаты почти одинаковы.

    public static int LookupHand(int[] cards)
    {
            int p = _lut[53 + cards[0]];
            p = _lut[p + cards[1]];
            p = _lut[p + cards[2]];
            p = _lut[p + cards[3]];
            p = _lut[p + cards[4]];
            p = _lut[p + cards[5]];
            return _lut[p + cards[6]];
    }
    

Вот цитата:

После удаления "небезопасных" частей кода и небольших корректировок в версия С# теперь также составляет около 310 млн.

Есть ли другой способ повысить производительность этой системы ранжирования рук?

4b9b3361

Ответ 1

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

Сказав это, я взглянул только на ваш метод Init(), и это оставило меня почесывать мою голову. Мне было трудно следовать. Мое эмпирическое правило для использования "небезопасного" заключается в том, чтобы не использовать его, если только я не должен этого делать. Этот метод Init(), я предполагаю, получает один раз, правильно? Я решил сравнить его:

static void BenchmarkIt(string input, Action myFunc)
{
    myWatch.Restart();
    myFunc();
    myWatch.Stop();

    Console.WriteLine(input, myWatch.ElapsedMilliseconds);
}

BenchmarkIt("Updated Init() Method:  {0}", Init2);
BenchmarkIt("Original Init() Method: {0}", Init1);  

Где Init1() - ваш исходный код, а Init2() - это мой перезаписанный код (я также несколько раз менял порядок ради справедливости). Вот что я получаю (на моей машине)...

Обновлен метод Init(): 110

Метод Initial Init(): 159

Вот код, который я использовал. Не требуется небезопасное ключевое слово.

public static void Init2()
{
    if (!File.Exists(fileName)) { throw new Exception("Handranks.dat not found"); }            

    BinaryReader reader = new BinaryReader(File.Open(fileName, FileMode.Open));            

    try
    {
        _lut = new int[maxSize];
        var tempBuffer = reader.ReadBytes(maxSize * 4); 
        Buffer.BlockCopy(tempBuffer, 0, _lut, 0, maxSize * 4);
    }
    finally
    {
        reader.Close();
    }
}

На мой взгляд, этот код легче читать и, кажется, работает быстрее.

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

Мне удалось запустить код 100 000 000 раз за 500 миллисекунд. Я работаю на довольно мускулистом 64-битном ноутбуке, который, кажется, является той скоростью, которую вы ожидали. Как говорили другие, работа в режиме выпуска (оптимизация) может оказать большое влияние на производительность.

Ответ 2

Если вам нужна общая скорость, я бы предложил использовать оценщика в Brecware: http://www.brecware.com/Software/software.html. Оценщик Стива Брехера быстрее, чем оценщик RayW для оценок, которые происходят в случайном порядке и гораздо более компактны.

Как отмечено в комментариях, оценщик RayW зависит от местоположения ссылки для его скорости. Если вы не проходите оценки в том же порядке, что и таблицы поиска, это будет медленным. Если это ваша проблема, есть три подхода:

  • Сделайте ваш оценочный заказ более точно сопоставимым с таблицами.
  • Сделать таблицы, соответствующие вашему порядку оценки
  • Сделайте оценщика оптимизированным для вашего использования.