2013-04-12 2 views
20

Вопроса являетсябыстрого внедрение log2 (INT) и log2 (поплавок)

Существует ли какая-либо другая (и/или быстрее) реализация основного 2log?

Приложения

log2 (INT) и log2 операции (с плавающей точкой), очень полезны во многих различных контекстах. Чтобы назвать несколько: алгоритмы сжатия, 3d-двигатели и машинное обучение. Почти во всех этих контекстах они используются в низкоуровневом коде, который называется миллиардами раз ... Особенно полезно использовать операцию log2 (int).

Поскольку я все время использую log2, я не хочу давать конкретное приложение, над которым я работаю здесь. То же самое происходит и в том, что это реальный осушитель производительности (как показывают тесты производительности различных приложений). Для меня это ключ к тому, чтобы сделать это как можно быстрее.

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

И, конечно же, запустите ваши тесты не менее 3 раз и убедитесь, что счетчики достаточно большие, чтобы нанести несколько секунд. Также я делаю операцию «добавить», чтобы весь цикл не был магически удален JIT'ter. Давайте начнем с настоящей работы.

Trivial реализация

тривиальная реализация 2log в C# является:

(int)(Math.Log(x)/Math.Log(2)) 

Этой реализация тривиальна, но очень медленно. Для этого требуется 2 Log-операции, которые уже сами по себе довольно медленные. Конечно, мы можем оптимизировать это, сделав константу 1.0/Math.Log(2).

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

Таблица поиска

Более быстрое решение этого заключается в том, чтобы использовать таблицу поиска. Хотя вы можете использовать таблицу поиска любой мощностью 2, я обычно использую таблицу размером 256 или 64 тыс. Записей.

Сначала мы создаем таблицу поиска:

lookup = new int[256]; 
for (int i = 1; i < 256; ++i) 
{ 
    lookup[i] = (int)(Math.Log(i)/Math.Log(2)); 
} 

Далее мы реализуем 2log следующим образом:

private static int LogLookup(int i) 
{ 
    if (i >= 0x1000000) { return lookup[i >> 24] + 24; } 
    else if (i >= 0x10000) { return lookup[i >> 16] + 16; } 
    else if (i >= 0x100) { return lookup[i >> 8] + 8; } 
    else { return lookup[i]; } 
} 

Как вы можете видеть, табличный поиск являются намного быстрее реализациями - но как con, он не может использоваться для вычисления log2(float).

удаление Отделения

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

nobranch = new int[16] { 0, 0, 8, 8, 16, 16, 16, 16, 24, 24, 24, 24, 24, 24, 24, 24 }; 

private static int LogDoubleLookup(int i) 
{ 
    int n = (i | (i >> 4)); 
    n = (n | (n >> 2)); 
    n = (n | (n >> 1)); 
    n = ((n & 0x1000000) >> 21) | ((n & 0x10000) >> 14) | ((n & 0x100) >> 7) | (n & 1); 
    int br = nobranch[n]; 
    return lookup[i >> br] + br; 
} 

Если запустить этот тест, вы увидите, что это на самом деле медленнее, чем если -те-другое решение.

А потом был Intel 80386

Intel понял года назад, что это важная операция, поэтому они реализовали Bit-Scan-Forward (BSF) в свои процессоры. Другие процессоры имеют схожие инструкции. Это, безусловно, самый быстрый способ сделать 2log, о котором я знаю, но, к сожалению, теперь я знаю, как использовать эти прекрасные функции из C# ... Мне не нравится идея иметь реализацию, которая больше не запускается когда на рынок выходит новый планшет или телефон - и я не знаю какого-либо кросс-платформенного решения, которое позволяет мне напрямую использовать эту функцию.

Другие реализации

Как l4V указал (спасибо!) Есть несколько других реализаций, в частности:

  • Trivial петля. Я опустил это, потому что это тривиально, это не очень быстро. Реализовано в TestTrivial.
  • 64-разрядное соединение IEEE/int, которое может использоваться. Реализовано в TestFloat
  • Таблицы поиска DeBruijn. Реализовано в TestDeBruijn
  • Двоичный поиск. Реализовано в TestBinary

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

Полный тестовый код

public class Log2Test 
{ 
    public static void TestNaive() 
    { 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     int n = 0; 
     for (int i = 1; i < 100000000; ++i) 
     { 
      n += (int)(Math.Log(i)/Math.Log(2.0)); 
     } 
     sw.Stop(); 
     Console.WriteLine("Result: {0} - naive implementation took {1:0.000}s", n, sw.Elapsed.TotalSeconds); 
    } 

    public static int LogTrivialLoop(int v) 
    { 
     int r = 0; 
     while ((v >>= 1) > 0) // unroll for more speed... 
     { 
      r++; 
     } 
     return r; 
    } 

    public static void TestTrivialLoop() 
    { 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     int n = 0; 
     for (int i = 1; i < 100000000; ++i) 
     { 
      n += LogTrivialLoop(i); 
     } 
     sw.Stop(); 
     Console.WriteLine("Result: {0} - loop implementation took {1:0.000}s", n, sw.Elapsed.TotalSeconds); 
    } 

    public static int LogFloat(int v) 
    { 
     Helper h = new Helper() { U1 = v, U2 = 0x43300000 }; 
     h.D -= 4503599627370496.0; 
     return (h.U2 >> 20) - 0x3FF; 
    } 

    public static void TestFloat() 
    { 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     int n = 0; 
     for (int i = 1; i < 100000000; ++i) 
     { 
      n += LogFloat(i); 
     } 
     sw.Stop(); 
     Console.WriteLine("Result: {0} - IEEE float implementation took {1:0.000}s", n, sw.Elapsed.TotalSeconds); 
    } 

    [StructLayout(LayoutKind.Explicit)] 
    private struct Helper 
    { 
     [FieldOffset(0)] 
     public int U1; 
     [FieldOffset(4)] 
     public int U2; 
     [FieldOffset(0)] 
     public double D; 
    } 

    public static void TestConstant() 
    { 
     double c = 1.0/Math.Log(2.0); 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     int n = 0; 
     for (int i = 1; i < 100000000; ++i) 
     { 
      n += (int)(0.00000000001 + Math.Log(i) * c); 
     } 
     sw.Stop(); 
     Console.WriteLine("Result: {0} - naive 2 implementation took {1:0.000}s", n, sw.Elapsed.TotalSeconds); 
    } 

    private static int LogLookup(int i) 
    { 
     if (i >= 0x1000000) { return lookup[i >> 24] + 24; } 
     else if (i >= 0x10000) { return lookup[i >> 16] + 16; } 
     else if (i >= 0x100) { return lookup[i >> 8] + 8; } 
     else { return lookup[i]; } 
    } 

    public static void TestLookup() 
    { 
     lookup = new int[256]; 
     for (int i = 1; i < 256; ++i) 
     { 
      lookup[i] = (int)(Math.Log(i)/Math.Log(2)); 
     } 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     int n = 0; 
     for (int i = 1; i < 100000000; ++i) 
     { 
      n += LogLookup(i); 
     } 
     sw.Stop(); 
     Console.WriteLine("Result: {0} - table lookup implementation took {1:0.000}s", n, sw.Elapsed.TotalSeconds); 
    } 

    private static int LogDoubleLookup(int i) 
    { 
     int n = (i | (i >> 4)); 
     n = (n | (n >> 2)); 
     n = (n | (n >> 1)); 
     n = ((n & 0x1000000) >> 21) | ((n & 0x10000) >> 14) | ((n & 0x100) >> 7) | (n & 1); 
     int br = nobranch[n]; 
     return lookup[i >> br] + br; 
    } 

    public static void TestDoubleLookup() 
    { 
     // Lookup table was already constructed earlier 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     int n = 0; 
     for (int i = 1; i < 100000000; ++i) 
     { 
      n += LogDoubleLookup(i); 
     } 
     sw.Stop(); 
     Console.WriteLine("Result: {0} - double table lookup implementation took {1:0.000}s", n, sw.Elapsed.TotalSeconds); 
    } 

    private static int LogBinary(int v) 
    { 
     /* This is the worst implementation ever... - apparently C# is a slow-branching language 

     int[] b = { 0x2, 0xC, 0xF0, 0xFF00, 0x7FFF0000 }; 
     int[] S = { 1, 2, 4, 8, 16 }; 

     int r = 0; // result of log2(v) will go here 
     for (int i = 4; i >= 0; i--) // unroll for speed... 
     { 
      if ((v & b[i]) != 0) 
      { 
       v >>= S[i]; 
       r |= S[i]; 
      } 
     } 
     return r; 

     */ 

     int r = (((v > 0xFFFF)) ? 0x10 : 0); 
     v >>= r; 
     int shift = ((v > 0xFF) ? 0x8 : 0); 
     v >>= shift; 
     r |= shift; 
     shift = ((v > 0xF) ? 0x4 : 0); 
     v >>= shift; 
     r |= shift; 
     shift = ((v > 0x3) ? 0x2 : 0); 
     v >>= shift; 
     r |= shift; 
     r |= (v >> 1); 
     return r; 
    } 

    public static void TestBinary() 
    { 
     // Lookup table was already constructed earlier 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     int n = 0; 
     for (int i = 1; i < 100000000; ++i) 
     { 
      n += LogBinary(i); 
     } 
     sw.Stop(); 
     Console.WriteLine("Result: {0} - binary search implementation took {1:0.000}s", n, sw.Elapsed.TotalSeconds); 
    } 

    private static readonly int[] MultiplyDeBruijnBitPosition = new int[32] 
    { 
     0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, 
     8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 
    }; 

    private static int LogDeBruijn(int v) 
    { 
     v |= v >> 1; // first round down to one less than a power of 2 
     v |= v >> 2; 
     v |= v >> 4; 
     v |= v >> 8; 
     v |= v >> 16; 

     return MultiplyDeBruijnBitPosition[(uint)(v * 0x07C4ACDDU) >> 27]; 
    } 

    public static void TestDeBruijn() 
    { 
     // Lookup table was already constructed earlier 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     int n = 0; 
     for (int i = 1; i < 100000000; ++i) 
     { 
      n += LogDeBruijn(i); 
     } 
     sw.Stop(); 
     Console.WriteLine("Result: {0} - de Bruijn implementation took {1:0.000}s", n, sw.Elapsed.TotalSeconds); 
    } 

    private static int[] lookup; 
    private static readonly int[] nobranch = new int[16] { 0, 0, 8, 8, 16, 16, 16, 16, 24, 24, 24, 24, 24, 24, 24, 24 }; 

    static void Main(string[] args) 
    { 
     TestConstant(); 
     TestNaive(); 
     TestDeBruijn(); 
     TestBinary(); 
     TestFloat(); 
     TestTrivialLoop(); 
     TestLookup(); 
     TestDoubleLookup(); 
     Console.ReadLine(); 
    } 
} 
+1

http://graphics.stanford.edu/~seander /bithacks.html#IntegerLogObvious – I4V

+0

@ I4V, это отличный ответ. – Ben

+0

@ l4V ах да, это те из книги Хакеры Восторг. Я проверю оставшиеся и добавлю их в код. Давайте посмотрим, как быстро они на C# ... Я был удивлен более одного раза ... – atlaste

ответ

3

There are some integer algorithms here.

В C#:

public static uint FloorLog2(uint x) 
{ 
    x |= (x >> 1); 
    x |= (x >> 2); 
    x |= (x >> 4); 
    x |= (x >> 8); 
    x |= (x >> 16); 

    return (uint)(NumBitsSet(x) - 1); 
} 

public static uint CeilingLog2(uint x) 
{ 
    int y = (int)(x & (x - 1)); 

    y |= -y; 
    y >>= (WORDBITS - 1); 
    x |= (x >> 1); 
    x |= (x >> 2); 
    x |= (x >> 4); 
    x |= (x >> 8); 
    x |= (x >> 16); 

    return (uint)(NumBitsSet(x) - 1 - y); 
} 

public static int NumBitsSet(uint x) 
{ 
    x -= ((x >> 1) & 0x55555555); 
    x = (((x >> 2) & 0x33333333) + (x & 0x33333333)); 
    x = (((x >> 4) + x) & 0x0f0f0f0f); 
    x += (x >> 8); 
    x += (x >> 16); 

    return (int)(x & 0x0000003f); 
} 

private const int WORDBITS = 32; 

Вы должны посмотреть на исходный код на сайте я связан в контексте, в частности, то, что происходит с log2 (0).

+0

Спасибо. Я пробовал конкретно «FloorLog2», потому что он похож на то, что я делаю. Однако, боюсь, я обнаружил, что алгоритм будет в два раза медленнее, чем DeBruijn и единственные поисковые решения ... – atlaste

+0

Интересно - хорошо, это стоило! :) –

+1

Конечно, спасибо. Я также был удивлен, обнаружив, что в просмотрах таблиц C# часто бывают быстрее, чем альтернативы ... Тем не менее, мне немного грустно, что с 1985 года есть инструкция почти со всех процессоров, которая делает всю работу без вычисления какой-либо вещи. просто кажется, что вы просто не в курсе, когда идете на «сложный язык» ... – atlaste

2

Для более алгоритмов смотрите здесь http://www.asmcommunity.net/forums/topic/?id=15010

также сделали некоторые испытания в C++ и моя реализация BSR медленнее, чем таблицы перекодировки

  • я использую BDS2006 там, вероятно, замедлится государства толчка/выскакивает в соответствии с директивой ассемблерного
  • вашего поиск это хорошо, но я с помощью 11 битой таблицы вместо 8
  • он делит 32 бит на 3 ветвь вместо 4
  • и это все еще достаточно мал, чтобы справиться без функции инициализации

код:

//--------------------------------------------------------------------------- 
DWORD log2_slow(const DWORD &x) 
    { 
    DWORD m,i; 
    if (!x) return 0; 
    if (x>=0x80000000) return 31; 
    for (m=1,i=0;m<x;m<<=1,i++); 
    if (m!=x) i--; 
    return i; 
    } 
//--------------------------------------------------------------------------- 
DWORD log2_asm(const DWORD &x) 
    { 
    DWORD xx=x; 
    asm { 
     mov eax,xx 
     bsr eax,eax; 
     mov xx,eax; 
     } 
    return xx; 
    } 
//--------------------------------------------------------------------------- 
BYTE _log2[2048]= 
    { 
    0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 
    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 
    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 
    9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 
    9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 
    9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 
    9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 
    10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 
    10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 
    10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 
    10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 
    10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 
    10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 
    10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 
    10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 
    }; 
DWORD log2(const DWORD &x) 
    { 
     if (x>=0x00400000) return _log2[x>>22]+22; 
    else if (x>=0x00000800) return _log2[x>>11]+11; 
    else     return _log2[x]; 
    } 
//--------------------------------------------------------------------------- 

тестовый код:

DWORD x,j,i,n=256; 
tbeg(); for (i=0;i<32;i++) for (j=0;j<n;j++) x=log2  (j<<i); tend(); mm_log->Lines->Add(tstr(1)); 
tbeg(); for (i=0;i<32;i++) for (j=0;j<n;j++) x=log2_asm (j<<i); tend(); mm_log->Lines->Add(tstr(1)); 
tbeg(); for (i=0;i<32;i++) for (j=0;j<n;j++) x=log2_slow(j<<i); tend(); mm_log->Lines->Add(tstr(1)); 

мои результаты на AMD A8-5500 3.2 ГГц:

[ 0.040 ms] log2  (x) - 11bit lookup table 
[ 0.060 ms] log2_asm (x) - BSR 
[ 0.415 ms] log2_slow(x) - shift loop 

Примечание:

  • log2 (0) -> 0 из-за использования DWORDs, в режиме реального времени он должен быть -inf
  • все остальные значения являются правильными для всех функций
+0

BSR заведомо медленнее на AMD (быстро на Intel) – harold

+0

может быть, но что ограничение на управление ASM замедляется ... на это наступило много раз (реализация asm медленнее, чем C++, если ускорение скорости ASM не преодолеет это состояние push/popping вещь) на данный момент единственное, что я ускорил asm, - это mul и div/mod из 32-разрядных операций uint. – Spektre

+0

Это тоже вы можете попробовать с помощью declspec (голый) или с простым asm, на который вы ссылаетесь? – harold

3

Взял бинарное решение уже упоминалось и удалили ветвление. Произошло некоторое тестирование, и он оказался в 1,3 раза быстрее, чем DeBruijn.

public static int Log2(int v) 
{ 
    int r = 0xFFFF - v >> 31 & 0x10; 
    v >>= r; 
    int shift = 0xFF - v >> 31 & 0x8; 
    v >>= shift; 
    r |= shift; 
    shift = 0xF - v >> 31 & 0x4; 
    v >>= shift; 
    r |= shift; 
    shift = 0x3 - v >> 31 & 0x2; 
    v >>= shift; 
    r |= shift; 
    r |= (v >> 1); 
    return r; 
} 
+0

Очень интересно; Некоторое время назад я создал ту же самую вещь на C++ (возможно, с внутренними свойствами AVX2), и она оказалась медленнее. Я собираюсь изучить это немного больше, когда у меня есть время, чтобы сэкономить, спасибо. – atlaste

+0

В моих тестах C# 4.0 этот метод выполнялся на 20% медленнее, чем 'int LogDeBruijn (int v)' в ответе atlaste (что также оказалось лучшим). –

+0

@SpecialSauce, вы поместили алгоритм внутри самого цикла for или метода, который вызывается в for-loop? Я всегда ставил алгоритм, который я тестировал внутри цикла for, потому что вызовы методов медленны в C#, и я не тестирую вызов метода. Также вы убедились, что вы протестировали его в режиме Release? – DanielSig

0

Вот что я использовал:

unsigned log2(register unsigned n) { 
    register unsigned i; 
    for (i=0; (n & 1); n>>=1, i++); 
    return i; 
} 

Edit: Проверить их (варианты ffz): https://patchwork.kernel.org/patch/8845631/

+0

Хотя это концептуально просто, этот метод довольно плохо выполняет значения 'long' с общим или неопределенным распределением. В C# 4.0 это заняло ** в 4,5 раза больше **, чем некоторые другие подходы. Единственный раз, когда этот метод может быть полезен, - это то, что распределение чисел уже известно, что все они близки к нулю, потому что тогда этот метод выходит раньше, проверяя лишь относительно немного бит. Я сомневаюсь, что ключевое слово «register» на C++ достаточно, чтобы компенсировать недостатки этого алгоритма. –

+0

Уверен, что это медленнее, чем таблица поиска или те магические заклинания, которые производят эти другие мастера, но не медленнее, чем использование реальных экспонентов и log2, я уверен. Ха-ха. В 4,5 раза дольше, если число далеко от нуля. Как это соотносится? Вы говорите, что в 4,5 раза больше, в каких ситуациях? Также проверьте [эти] (https://patchwork.kernel.org/patch/8845631/). Я не знаю, есть ли у вас их на C#. – quirinpa

1
inline int fast_log2(register double x) 
{ 
    return (reinterpret_cast<uint64_t&>(x) >> 52) - 1023; 
};