Non chiedermi come ci sono arrivato, ma giocavo con un po 'di mascheramento, srotolamento di loop ecc. In ogni caso, per interesse stavo pensando a come avrei implementato un metodo indexof, e per farla breve, tutto ciò mascherando ecc., questa ingenua implementazione:Perché il mio string.indexof (char) è più veloce?
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;
}
}
è più veloce di string.IndexOf (char). Ho scritto alcuni test semplici e sembra che corrisponda esattamente all'output. Alcuni numeri di output di esempio dalla mia macchina (che varia in una certa misura, naturalmente, ma la tendenza è chiara):
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 è contrassegnato extern, quindi potete riflettore esso. Comunque penso che questa dovrebbe essere l'implementazione (nativa): http://www.koders.com/cpp/fidAB4768BA4DF45482A7A2AA6F39DE9C272B25B8FE.aspx?s=IndexOfChar#L1000
Sembra che usino la stessa implementazione ingenua.
domande mi vengono in mente:
1) mi sto perdendo qualcosa nella mia implementazione che spiega il motivo per cui il suo più veloce? Posso solo pensare al supporto esteso dei caratteri, ma la loro implementazione suggerisce che non fanno nulla di speciale neanche per quello.
2) Ho pensato che molti dei metodi di basso livello sarebbero stati implementati in assembler manuale, il che non sembra essere il caso. In tal caso, perché implementarlo in modo nativo, invece di solo in C# come la mia implementazione di esempio?
(Test completo qui (penso che sia troppo lungo per incollare qui): http://paste2.org/p/1606018)
(No questo non è l'ottimizzazione prematura, non è per un progetto che sto solo prendendo in giro circa) :-)
Aggiornamento: da Thnx a Oliver per l'indicazione su nullcheck e il parametro Count. Ho aggiunto questi al mio IndexOf16Implementation in questo modo:
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;
}
}
I numeri cambiato un po ', ma è ancora abbastanza significativamente più veloce (32/64 risultati omessi):
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: questa versione è più veloce ma (soprattutto per il lungo caso pagliaio):
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;
}
}
Update 4: Sulla base della discussione con LastCoder credo che dipenda dall'architettura. Il mio Xeon W3550 alle opere sembra preferire questa versione, mentre il suo i7 sembra apprezzare la versione buildin. La mia macchina domestica (Athlon II) sembra essere nel mezzo. Sono sorpreso dalla grande differenza però.
mask1 deve essere 0xffff anziché 0xff – hazzik
@hazzik thnx per il suggerimento – chrisaut