Domanda

Sto eseguendo il porting di un'applicazione esistente su C # e voglio migliorare le prestazioni ove possibile. Molti contatori di loop e riferimenti di array esistenti sono definiti come System.UInt32, anziché Int32 che avrei usato.

Esistono differenze significative nelle prestazioni nell'uso di UInt32 vs Int32?

È stato utile?

Soluzione

Non credo ci siano considerazioni sulle prestazioni, oltre alla possibile differenza tra l'aritmetica firmata e non firmata a livello di processore, ma a quel punto penso che le differenze siano controverse.

La differenza maggiore sta nella conformità CLS poiché i tipi non firmati non sono conformi a CLS poiché non tutte le lingue li supportano.

Altri suggerimenti

La risposta breve è " No. Qualsiasi impatto sulle prestazioni sarà trascurabile " ;.

La risposta corretta è " dipende. "

Una domanda migliore è, " dovrei usare uint quando sono certo di non aver bisogno di un segno? "

Il motivo per cui non puoi dare un "sì" definitivo o " no " per quanto riguarda le prestazioni è perché la piattaforma di destinazione alla fine determinerà le prestazioni. Cioè, le prestazioni sono dettate da qualunque processore stia eseguendo il codice e dalle istruzioni disponibili. Il tuo codice .NET viene compilato in Intermediate Language (IL o Bytecode). Queste istruzioni vengono quindi compilate sulla piattaforma di destinazione dal Just-In-Time (JIT) come parte del Common Language Runtime (CLR). Non puoi controllare o prevedere quale codice verrà generato per ogni utente.

Quindi, sapendo che l'hardware è l'arbitro finale delle prestazioni, la domanda diventa, "Quanto è diverso il codice che NET genera per un intero con segno rispetto a senza segno?" e " La differenza influenza la mia applicazione e le mie piattaforme target? "

Il modo migliore per rispondere a queste domande è eseguire un test.

class Program
{
  static void Main(string[] args)
  {
    const int iterations = 100;
    Console.WriteLine(
Signed:      00:00:00.5066966

Unsigned:    00:00:00.5052279
quot;Signed: {Iterate(TestSigned, iterations)}"); Console.WriteLine(<*>quot;Unsigned: {Iterate(TestUnsigned, iterations)}"); Console.Read(); } private static void TestUnsigned() { uint accumulator = 0; var max = (uint)Int32.MaxValue; for (uint i = 0; i < max; i++) ++accumulator; } static void TestSigned() { int accumulator = 0; var max = Int32.MaxValue; for (int i = 0; i < max; i++) ++accumulator; } static TimeSpan Iterate(Action action, int count) { var elapsed = TimeSpan.Zero; for (int i = 0; i < count; i++) elapsed += Time(action); return new TimeSpan(elapsed.Ticks / count); } static TimeSpan Time(Action action) { var sw = new Stopwatch(); sw.Start(); action(); sw.Stop(); return sw.Elapsed; } }

I due metodi di test, TestSigned e TestUnsigned , eseguono ciascuno ~ 2 milioni di iterazioni di un semplice incremento su un intero con segno e senza segno, rispettivamente. Il codice del test esegue 100 iterazioni di ciascun test e calcola la media dei risultati. Ciò dovrebbe eliminare eventuali incongruenze. I risultati sul mio i7-5960X compilato per x64 sono stati:

<*>

Questi risultati sono quasi identici, ma per ottenere una risposta definitiva, dobbiamo davvero guardare al bytecode generato per il programma. Possiamo usare ILDASM come parte di .NET SDK per ispezionare il codice nell'assembly generato dal compilatore.

 Bytecode

Qui, possiamo vedere che il compilatore C # favorisce numeri interi con segno e in realtà esegue la maggior parte delle operazioni in modo nativo come numeri interi con segno e tratta sempre e solo il valore in memoria come non firmato quando si confronta per il ramo (a.k.a jump o if). Nonostante utilizziamo un numero intero senza segno sia per l'iteratore che per l'accumulatore in TestUnsigned , il codice è quasi identico al metodo TestSigned tranne che per una singola istruzione: IL_0016 . Una rapida occhiata alle Specifiche ECMA descrive la differenza:

  

blt.un.s:   Diramazione verso target se inferiore a (non firmato o non ordinato), forma abbreviata.

     

blt.s:   Diramazione per target se inferiore a, forma abbreviata.

Essendo un'istruzione così comune, è lecito ritenere che la maggior parte dei moderni processori ad alta potenza disporranno di istruzioni hardware per entrambe le operazioni e molto probabilmente verranno eseguite nello stesso numero di cicli, ma questo non è garantito . Un processore a bassa potenza può avere meno istruzioni e non avere un ramo per int senza segno. In questo caso, il compilatore JIT potrebbe dover emettere più istruzioni hardware (una conversione prima, quindi un ramo, per esempio) per eseguire l'istruzione blt.un.s IL. Anche in questo caso, queste istruzioni aggiuntive sarebbero di base e probabilmente non influirebbero sulle prestazioni

Non ho fatto alcuna ricerca sull'argomento in .NET, ma ai vecchi tempi di Win32 / C ++, se volevi lanciare un "firmato int" " a un "lungo firmato", la cpu ha dovuto eseguire un'operazione per estendere il segno. Per lanciare un int "unsigned int" a un "lungo senza segno", aveva solo roba zero nei byte superiori. Il risparmio era nell'ordine di un paio di cicli di clock (cioè, dovresti farlo miliardi di volte per avere una differenza persino percepibile)

Non c'è differenza, per quanto riguarda le prestazioni. I calcoli di interi semplici sono ben noti e i moderni CPU sono altamente ottimizzati per eseguirli rapidamente.

Questi tipi di ottimizzazioni raramente valgono la pena. Utilizzare il tipo di dati più appropriato per l'attività e lasciarlo a quello. Se questa cosa tocca un database, probabilmente potresti trovare una dozzina di modifiche nella progettazione del DB, nella sintassi delle query o nella strategia di indicizzazione che compenserebbero un'ottimizzazione del codice in C # di alcune centinaia di ordini di grandezza.

Sta per allocare la stessa quantità di memoria in entrambi i modi (anche se uno può memorizzare un valore più grande, in quanto non risparmia spazio per il segno). Quindi dubito che vedrai una differenza di "prestazioni", a meno che tu non usi grandi valori / valori negativi che faranno esplodere un'opzione o l'altra.

questo non ha davvero a che fare con le prestazioni piuttosto che con i requisiti per il contatore loop.

Probabilmente ci sono state molte iterazioni da completare

        Console.WriteLine(Int32.MaxValue);      // Max interation 2147483647
        Console.WriteLine(UInt32.MaxValue);     // Max interation 4294967295

L'int non firmato potrebbe essere lì per un motivo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top