Можно ли оптимизировать этот код? - PullRequest
1 голос
/ 08 января 2009

Меня интересует скорость, а не красивый код, поэтому я использую массив, а не список (целого числа).

У меня есть массив, похожий на: 0,1,0,1,1,0,1,0,1,1,1,0,0,1

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

Итак, я делаю цикл по массиву, чтобы получить номер позиции каждого 1, а затем создаю новый массив, похожий на этот: 2,4,5,7,9,10,11,14

это может быть побитовое здесь? Понятия не имею

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

Private Function theThing() As Integer()
    Dim x As Integer
    'arIn() would be a parameter
    Dim arIn() As Integer = {0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1}
    Dim ar() As Integer = Nothing
    Dim arCount As Integer = -1

    For x = 1 To arIn.GetUpperBound(0)
        If arIn(x) = 1 Then
            arCount += 1
        End If
    Next

    If arCount > -1 Then
        'using redim preseve is slower than the loop above
        ReDim ar(arCount)

        arCount = 0
        For x = 1 To arIn.GetUpperBound(0)
            If arIn(x) = 1 Then
                ar(arCount) = x
                arCount += 1
            End If
        Next
    End If

    Return ar
End Function

* РЕДАКТИРОВАТЬ *

текущее решение (на 10-15% быстрее) теперь

Private Function theThing() As Integer
    Dim ar() As Integer = {0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1}
    Dim arLenght As Integer = ar.GetUpperBound(0)
    Dim arCount As Integer = 0
    Dim x As Integer

    For x = 1 To arLenght
        If ar(x) = 1 Then
            ar(arCount) = x
            arCount += 1
        End If
    Next

    dim r As New Random()

    Return ar(r.Next(arCount))
End Function

Я не думаю, что его можно оптимизировать больше, если только кто-то не найдет способ сделать именно то, что делает решение, но гораздо быстрее

До этого вопроса я мог делать около 25500 пробежек каждые 10 секунд.

Теперь он может работать более 32250 раз, увеличение на 21%, спасибо!

Ответы [ 11 ]

8 голосов
/ 08 января 2009

Вместо того, чтобы хранить массив целых чисел, почему бы не поместить их все в одно целое число?

oldArray = [0, 1, 1, 0, 1]
newValue =  22     (binary 10110)

Если вы хотите проверить, установлена ​​ли конкретная битовая позиция, выполните побитовое сравнение с двумя значениями этой позиции:

is position 2 set?
value:    10110
4 (2^2):  00100
result:   00100 --> true

is position 0 set?
value:    10110
1 (2^0):  00001
result:   00000 --> false

Выполните поиск для побитового сравнения, и вы должны найти много помощи.

Вот несколько хороших вопросов о переполнении стека, которые могут помочь:
Что такое побитовые операторы?
Как установить, очистить и переключить один бит?

2 голосов
/ 08 января 2009

Несколько советов по оригинальному алгоритму:

  1. Попробуйте сохранить результаты arIn.GetUpperBound (0) в переменной. Я не знаю, как VB делает свои циклы, но есть вероятность, что функция вызывается один раз за каждую итерацию. Вы должны проверить это, хотя.
  2. То, что If arCount > -1 всегда будет правдой. Удалить его.

Если вы хотите сохранить те же самые входы / выходы, то я не думаю, что есть что-то еще, что можно улучшить.

Теперь, если вам нужна функция, которая также выполняет случайный выбор, то это может быть немного лучше. Я напишу на C #, так как знаю лучше. Вы должны быть в состоянии понять:

public static int GetRandomSetBit(int[] AllBits)
{
    // Perhaps check here if AllBits is null/empty. I'll skip that for now.

    int L = AllBits.Length;
    int SetBitCount = 0;

    // No point to save a few bytes here. Also - you can make a global array
    // large enough for all occasions and skip allocating it every time.
    // In that case consider automatic resizing and watch out for
    // multithreading issues (if you use several threads).
    int[] GoodPositions = new int[L];

    for ( int i = 0; i < L; i++ )
        if ( AllBits[i] != 0 )
        {
            GoodPositions[SetBitCount] = i;
            SetBitCount++;
        }
     Random r = new Random(); // Should use one global instance
     return GoodPositions[r.Next(SetBitCount)];
}

Боюсь, лучше не станет. Нет, если вы не можете каким-то образом изменить входы / выходы или требования.

1 голос
/ 08 января 2009

Я думаю, что когда Рекурсив цитировал BitArray, он имел в виду что-то вроде этого:

using System.Collections.Generic;
using System;
using System.Collections;

class Program
{
    static void Main(string[] args)
    {
        var input = new BitArray(new byte[] { 0x5A /*01011010b*/
                                        , 0xE4 /*11100100b*/ });
        var output = new List<int>();
        var offset = 1;
        foreach (var bit in input)
        {
            if (bit)
            {
                output.Add(offset);
            }
            offset++;
        }
    }
}
1 голос
/ 08 января 2009

Отключить проверку переполнения.
Свойства проекта -> Компиляция -> Дополнительные параметры компиляции -> Удалить проверки целочисленного переполнения.
Если вам нужна проверка переполнения для остальной части проекта, вы можете переместить код в новый проект (например, DLL) и отключить проверку переполнения для этого нового проекта.

Также убедитесь, что вы используете сборку релиза (оптимизация включена) и не отлаживаете ее.

РЕДАКТИРОВАТЬ: я получаю 8,5 с (12 с, если я объявляю массив внутри For, который я использую для тестирования) для вызова функции 50 миллионов раз. Если вы получаете только 32000, то вы используете очень большие входные данные или что-то замедляет ваш код. Например, если вы подсчитываете время внутри программы и запускаете его в профилировщике, вы получите неправильные результаты, поскольку профилирование может значительно замедлить программу. Также такие глюки Медленные вызовы методов могут повлиять на производительность.

1 голос
/ 08 января 2009

Мне трудно поверить, что redim preserve будет медленнее, чем ваш цикл, если он сам не будет внутри цикла.

В этом случае, для необработанной скорости, не считайте количество единиц в arIn только для того, чтобы установить размер ar. Поскольку ar не может никогда быть больше, чем arIn, просто установите его на тот же размер и сохраните redim-preserve в конце (не будет медленнее, поскольку он находится вне цикла и всегда будет обрезаться, а не расширяться - VB надеюсь, можно сделать это на месте, а не выделять больше памяти). Кроме того, размер кэша arIn в случае, если VB вычисляет его каждый раз в цикле (вероятно, если разрешены ReDim).

Private Function theThing() As Integer()
    Dim x As Integer
    'arIn() would be a parameter
    Dim arIn() As Integer = {0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1}
    Dim ar(arIn.GetUpperBound(0)) As Integer
    Dim arCount As Integer
    Dim arInCount As Integer

    arCount = 0
    arInCount = arIn.GetUpperBound(0)
    For x = 1 To arInCount
        If arIn(x) = 1 Then
            ar(arCount) = x
            arCount += 1
        End If
    Next

    ReDim Preserve ar(arCount)
    Return ar
End Function

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

Для вашего примера возвращаемый массив будет:

{8,2,4,5,7,9,10,11,14,?,?,?,?,?,?} (? values are irrelevant).
 ^ <-------+--------> <----+---->
 |         |               |
 |         |               +-- These are unused.
 |         |
 |         +-- These are used.
 |
 +-- This is the count of elements to use.

Этот код будет:

Private Function theThing() As Integer()
    Dim x As Integer
    'arIn() would be a parameter
    Dim arIn() As Integer = {0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1}
    Dim ar(arIn.GetUpperBound(0)+1) As Integer
    Dim arCount As Integer
    Dim arInCount As Integer

    arCount = 0
    arInCount = arIn.GetUpperBound(0)
    For x = 1 To arInCount
        If arIn(x) = 1 Then
            ar(arCount) = x
            arCount += 1
        End If
    Next

    ar(0) = arCount
    Return ar
End Function

Затем в вашем коде, который выбирает случайное значение из ar, вместо:

rndval = rnd(ar.GetUpperBound)

использование:

rndval = rnd(ar(0) + 1)
0 голосов
/ 08 января 2009

Я пробовал другой подход. Идея состоит в том, чтобы продолжать получать случайную запись и выходить, когда найдена запись, соответствующая 1. Это решение не идеально, потому что некоторые случайные числа не используются, которые могут или не могут сломать случайность. Кроме того, скорость сильно зависит от плотности «1» с. Код следующий:

public static int GetRandomSetBit(int[] AllBits)
{
    int L = AllBits.Length;
    Random r = new Random();    // Better use a global instance as this hurt performance a lot
    do
    {
         selected = r.Next(L);

    } while (AllBits[selected] == 0);
    return selected;
}

На моем ПК, если создание объекта Random перемещено в глобальный объект, он может выполнить 50000000 испытаний примерно за 11 с, если есть 5 "1" из 30. Если есть до 15 "1", время, необходимое для этого, сокращается до 5 секунд.

По сравнению с кодом, предложенным Вилксом, его код может запустить 50000000 испытаний на моем ПК около 13 секунд

0 голосов
/ 08 января 2009

Если легко сгенерировать простое число, немного превышающее длину вашего массива (в зависимости от его размера, это может или не может быть простым), и вы не заботитесь о полностью равномерно случайном, то вы можете генерировать случайное число в этом диапазоне и генерировать этот цикл. Он должен найти ответ в течение нескольких итераций (в зависимости от плотности 1 с). Исходный код на c #, так как я не помню синтаксис vb:

int RandomInArray(int[] ar)
{
    int p = GenerateSlightlyLargerPrime(ar.Length);

    int x = random.nextInt(p);
    int i = x;
    do
    {
       if (i < ar.Length && ar[i] == 1)
           return i;
       i = (i + x) % p;
    } while (i != x);
    return -1;
}

Обратите внимание, что это не на 100% случайно, но должно быть достаточно близко.

0 голосов
/ 08 января 2009

Может быть, вы можете выжать максимальную производительность, используя таблицу преобразования. А затем примените алгоритм ниже:

Извините, я больше не говорю на VB, поэтому мне придется писать на C #. Вот часть всего кода, так как весь lookupTable будет иметь 256 элементов.

using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        var input = new byte[] { 0x5A /*01011010b*/, 0xE4 /*11100100b*/ };
        var output = new List<int>();
        var lookupTable = new int[][] { new int[0] /*00000000b*/
                                           , new int[] { 8 }    /*00000001b*/
                                           , new int[] { 7 }    /*00000010b*/
                                           , new int[] { 7,8 }  /*00000011b*/
                                           , new int[] { 6 }    /*00000100b*/
                                           , new int[] { 6,8 }  /*00000101b*/
                                           , new int[] { 6,7,8 }  /*00000111b*/
                                          };
        int offset = 0;
        foreach (var value in input)
        {
            foreach (var position in lookupTable[(int)value])
            {
                output.Add(position + offset);
            }
            offset += 8;
        }
    }
}
0 голосов
/ 08 января 2009

Это то, для чего BitArray был создан.

0 голосов
/ 08 января 2009

Вы можете обнаружить, что использование For Each и списка, инициализированного по крайней мере с длиной входного массива, быстрее, чем индексатор:

If arIn.Length > 0 Then
  Dim lstOut As New List(Of Integer)(arIn.Length)
  Dim ix As Integer = 0
  For Each val As Integer In arIn
    If val = 1 Then
      lstOut.Add(ix)
    End If
    ix = ix + 1
  End If
  Return lstOut.ToArray()
Else
  Return Nothing
End If

Но вы должны это проверить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...