Почему мой string.indexof (char) быстрее? - PullRequest
16 голосов
/ 24 августа 2011

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

public static unsafe int IndexOf16(string s, int startIndex, char c) {
            if (startIndex < 0 || startIndex >= s.Length) throw new ArgumentOutOfRangeException("startIndex");
            fixed (char* cs = s) {
                for (int i = startIndex; i < s.Length; i++) {
                    if ((cs[i]) == c) return i;
                }
                return -1;
            }
        }

быстрее, чем string.IndexOf (char). Я написал несколько простых тестов, и, похоже, они точно соответствуют результатам. Некоторые примеры выходных чисел с моей машины (конечно, они меняются в некоторой степени, но тенденция ясна):

short haystack 500k runs
1741 ms for IndexOf16
2737 ms for IndexOf32
2963 ms for IndexOf64
2337 ms for string.IndexOf <-- buildin

longer haystack:
2888 ms for IndexOf16
3028 ms for IndexOf32
2816 ms for IndexOf64
3353 ms for string.IndexOf <-- buildin

IndexOfChar помечен как внешний, поэтому вы не можете отразить его. Однако я думаю, что это должна быть (нативная) реализация: http://www.koders.com/cpp/fidAB4768BA4DF45482A7A2AA6F39DE9C272B25B8FE.aspx?s=IndexOfChar#L1000

Кажется, они используют одну и ту же наивную реализацию.

Вопросы приходят мне в голову:

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

2) Я предполагал, что большая часть низкоуровневых методов в конечном итоге будет реализована в ручном ассемблере, что, похоже, не так. Если это так, зачем вообще его реализовывать, а не только в C #, как в моем примере реализации?

(Завершите тестирование здесь (я думаю, что здесь слишком долго вставлять): http://paste2.org/p/1606018)

(Нет, это не преждевременная оптимизация, это не для проекта, о котором я просто балуюсь): -)

Обновление : Спасибо Оливеру за подсказку о нулевой проверке и параметре Count. Я добавил их в свой IndexOf16Implementation так:

public static unsafe int IndexOf16(string s, int startIndex, char c, int count = -1) {
    if (s == null) throw new ArgumentNullException("s");
    if (startIndex < 0 || startIndex >= s.Length) throw new ArgumentOutOfRangeException("startIndex");
    if (count == -1) count = s.Length - startIndex;
    if (count < 0 || count > s.Length - startIndex) throw new ArgumentOutOfRangeException("count");

    int endIndex = startIndex + count;
    fixed (char* cs = s) {
        for (int i = startIndex; i < endIndex; i++) {
            if ((cs[i]) == c) return i;
        }
        return -1;
    }
}

Числа изменились незначительно, однако это все еще довольно значительно быстрее (результаты 32/64 опущены):

short haystack 500k runs
1908 ms for IndexOf16
2361 ms for string.IndexOf
longer haystack:
3061 ms for IndexOf16
3391 ms for string.IndexOf

Обновление2 : эта версия еще быстрее (особенно для случая с длинным стогом сена):

public static unsafe int IndexOf16(string s, int startIndex, char c, int count = -1) {
            if (s == null) throw new ArgumentNullException("s");
            if (startIndex < 0 || startIndex >= s.Length) throw new ArgumentOutOfRangeException("startIndex");
            if (count == -1) count = s.Length - startIndex;
            if (count < 0 || count > s.Length - startIndex) throw new ArgumentOutOfRangeException("count");

            int endIndex = startIndex + count;
            fixed (char* cs = s) {
                char* cp = cs + startIndex;
                for (int i = startIndex; i <= endIndex; i++, cp++) {
                    if (*cp == c) return i;
                }
                return -1;
            }
        }

Обновление 4: Основываясь на обсуждении с LastCoder, я считаю, что это зависит от архитектуры. Мой Xeon W3550 на работе, кажется, предпочитает эту версию, в то время как его i7, похоже, нравится встроенная версия. Моя домашняя машина (Athlon II) кажется промежуточной. Я удивлен такой большой разницей.

Ответы [ 3 ]

4 голосов
/ 24 августа 2011

Возможность 1) Это может не выполняться (как истина) в C #, но когда я выполнил работу по оптимизации для ассемблера x86-64, я быстро обнаружил, что при сравнительном тестировании вызов кода из DLL (помеченный как внешний) был медленнее, чем реализация того же самогофункция в моем исполняемом файле.Наиболее очевидная причина - подкачка страниц и память, DLL-метод (внешний) загружается в память далеко от остальной части исполняемого кода, и, если к нему ранее не обращались, его нужно будет выполнять.некоторые циклы прогрева функций, которые вы тестируете, чтобы убедиться, что они сначала помещаются в память перед тем, как вы их синхронизируете.

Возможность 2) Microsoft стремится не оптимизировать строковые функции в полной мере, поэтому оптимизирует нативную строкудлина, подстрока, indexof и т. д. на самом деле не неслыханно.Анекдот;в ассемблере x86-64 мне удалось создать версию функции WinXP64 RtlInitUnicodeString, которая работала в 2 раза быстрее почти во всех случаях практического использования.

Возможность 3) Ваш тестовый код показывает, что вы используете перегрузку 2 параметров дляIndexOf, эта функция, вероятно, вызывает перегрузку 3 параметра IndexOf (Char, Int32, Int32), которая добавляет дополнительные издержки к каждой итерации.


Это может быть даже быстрее, потому что вы удаляете инкремент переменной i на каждую итерацию.

            char* cp = cs + startIndex;
            char* cpEnd = cp + endIndex;
            while (cp <= cpEnd) {
                if (*cp == c) return cp - cs;
                cp++;
            }

edit В ответ на вопрос (2) о вашем любопытстве, написанном еще в 2005 году и использовавшимся для исправления ntdll.dll моей машины WinXP64.http://board.flatassembler.net/topic.php?t=4467

RtlInitUnicodeString_Opt: ;;rcx=buff rdx=ucharstr 77bytes
             xor    r9d,r9d
             test   rdx,rdx
             mov    dword[rcx],r9d
             mov    [rcx+8],rdx
             jz     .end
             mov    r8,rdx
   .scan:
             mov    eax,dword[rdx]

             test   ax,ax
             jz     .one
             add    rdx,4
             shr    eax,16
             test   ax,ax
             jz     .two
             jmp    .scan
   .two:
             add    rdx,2
   .one:
             mov    eax,0fffch
             sub    rdx,r8
             cmp    rdx,0fffeh
             cmovnb rdx,rax
             mov    [ecx],dx
             add    dx,2
             mov    [ecx+2],dx
             ret
   .end:
             retn  

edit 2 Запуск вашего примера кода (обновлен с вашей самой быстрой версией) string.IndexOf работает быстрее на моем Intel i7, 4 ГБ оперативной памяти, Win7 64bit.

short haystack 500k runs
2590 ms for IndexOf16
2287 ms for string.IndexOf
longer haystack:
3549 ms for IndexOf16
2757 ms for string.IndexOf

Оптимизации иногда очень зависят от архитектуры.

2 голосов
/ 24 августа 2011

Если вы действительно делаете такую ​​микро-проверку, то учитывается каждый бит. В реализации MS (как видно из указанной вами ссылки) они также проверяют, является ли s нулевым, и выдают исключение NullArgumentException. Также это реализация, включающая параметр count. Таким образом, они дополнительно проверяют правильность значения и выдают исключение ArgumentOutOfRangeException.

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

1 голос
/ 24 августа 2011

Это может иметь отношение к «фиксированному» выражению, так как «оно фиксирует расположение объектов src и dst в памяти, чтобы они не были перемещены сборщиком мусора». возможно ускорение методов?

Также «Небезопасный код повышает производительность, избавляясь от проверок границ массива». это также может быть причиной.

Выше комментарии взяты из MSDN

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