No preguntes cómo llegué allí, pero estaba jugando con algo de enmascaramiento, desenrollado de lazo, etc. En cualquier caso, por interés, estaba pensando cómo implementaría un índice de método, y para resumir, todo eso enmascaramiento, etc. a un lado, esta implementación ingenua:¿Por qué mi string.indexof (char) es más rápido?
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;
}
}
es más rápido que string.IndexOf (char). Escribí algunas pruebas simples, y parece coincidir exactamente con la salida. Algunos números de salida de muestra de mi máquina (que varía en cierto grado, por supuesto, pero la tendencia es clara):
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 está marcado externo, por lo que no puede reflector de ella. Sin embargo, creo que esta debería ser la implementación (nativa): http://www.koders.com/cpp/fidAB4768BA4DF45482A7A2AA6F39DE9C272B25B8FE.aspx?s=IndexOfChar#L1000
Parece que usan la misma implementación ingenua.
Las preguntas vienen a la mente:
1) ¿Me estoy perdiendo algo en mi aplicación que explica por qué es más rápido? Solo puedo pensar en el soporte de caracteres extendidos, pero su implementación sugiere que tampoco hacen nada especial para eso.
2) Supuse que gran parte de los métodos de bajo nivel finalmente se implementarían en el ensamblador manual, eso no parece ser el caso. Si es así, ¿por qué implementarlo de forma nativa en absoluto, en lugar de simplemente en C# como mi implementación de muestra?
(prueba completa aquí (creo que es demasiado largo para pegar aquí): http://paste2.org/p/1606018)
(No, esto no es la optimización prematura, no es para un proyecto que estoy solo Juguetón) :-)
Update: Thnx a Oliver por la pista sobre nullcheck y Count param. He añadido estas a mi IndexOf16Implementation así:
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;
}
}
Los números cambian ligeramente, sin embargo, es todavía bastante significativamente más rápido (32/64 resultados omitidos):
short haystack 500k runs
1908 ms for IndexOf16
2361 ms for string.IndexOf
longer haystack:
3061 ms for IndexOf16
3391 ms for string.IndexOf
Update2: Esta versión sin embargo, es más rápido (sobre todo para el caso pajar largo):
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;
}
}
actualización 4: Basado en la discusión con LastCoder, creo que esto depende de la arquitectura. Mi Xeon W3550 en las obras parece preferir esta versión, mientras que a su i7 parece gustarle la versión buildin. Mi máquina doméstica (Athlon II) parece estar en el medio. Sin embargo, estoy sorprendido por la gran diferencia.
mask1 debe ser 0xffff en lugar de 0xff – hazzik
@hazzik thnx para la sugerencia – chrisaut