2012-03-14 14 views
6

Sto ancora cercando di trovare un modo veloce per convertire un array generico di tipo TOutput in un altro array di tipo TInput. Tutti i miei array hanno sempre un tipo di dati numerico, ma poiché C# non ha vincoli di tipo numerico come spesso richiesto, attualmente devo convivere con questo vincolo. I metodi suggeriti, come il casting su un oggetto prima, sembrano rallentare il mio cast tremendamente. Attualmente ho un grande costrutto if/else che controlla il tipo e il cast in un tipo definito usando l'aritmetica del puntatore, ma questo è il modo per gestire il futuro. Parallelamente .Per un buon modo per sbarazzarsi dei puntatori e per accelerare le cose, ma i vincoli generici di C# sembrano essere un problema, ma il problema è ancora presente nel codice sottostante. Ecco il mio codice:Come lanciare un array generico in un altro tipo?

public static OutputType[] Cast<InputType, OutputType>(InputType[] inputArray_in) 
    { 
     var aRange = Partitioner.Create(0, inputArray_in.Length); 
     OutputType[] aResult = new OutputType[inputArray_in.Length]; 

     Parallel.ForEach(aRange, (r) => 
     { 
      for (int i = r.Item1; i < r.Item2; i++) 
      { 
       aResult[i] = (OutputType)(inputArray_in[i]); 
      } 
     }); 

     return aResult; 
    } 

Esempio:

float[] A = { 0.1f, 0.2f, 0.6f }; 
int []B = Cast<float, int>(A); 

In tutti i casi miei tipi di array sono valori numerici (float, breve, doppio, ...) e la maggior parte del tempo, gli array sono circa Immagini 512x512, ma in una pila di circa 1000 fette in un volume. Vedi qualche possibilità di avere un modo semplice per farlo?

codice di prova

public static class CastTest 
{ 
    delegate double[] CastMethod(int[] input); 

    public static unsafe double[] Cast1(int[] input) 
    { 
     int N = input.Length; 
     double[] output = new double[N]; 

     for (int i = 0; i < N; i++) output[i] = (double)(input[i]); 

     return output; 
    } 

    public static unsafe double[] Cast2(int[] input) 
    { 
     int N = input.Length; 
     double[] output = new double[N]; 

     fixed (double* output_pinned = output) 
     { 
      double* outp = output_pinned; 

      fixed (int* input_pinned = input) 
      { 
       int* inp = input_pinned; 

       for (int i = 0; i < N; i++, inp++, outp++) *outp = (double)(*inp); 
      } 

      return output; 
     } 
    } 

    public static unsafe double[] Cast3(int[] input) 
    { 
     int N = input.Length; 
     double[] output = new double[N]; 

     fixed (double* output_pinned = output) 
     { 
      double* outp = output_pinned; 

      fixed (int* input_pinned = input) 
      { 
       int* inp = input_pinned; 

       for (int i = 0; i < N; i++) outp[i] = (double)(inp[i]); 
      } 

      return output; 
     } 
    } 

    public static unsafe double[] Cast4(int[] input) 
    { 
     int N = input.Length; 
     double[] output = new double[N]; 

     fixed (double* output_pinned = output) 
     { 
      fixed (int* input_pinned = input) 
      { 
       for (int i = 0; i < N; i++) output_pinned[i] = (double)(input_pinned[i]); 
      } 
     } 

     return output; 
    } 

    public static unsafe double[] Cast5(int[] input) 
    { 
     return Array.ConvertAll<int, double>(input, x => (double)x); 
    } 

    public static double[] Cast6(int[] input) 
    { 
     var aRange = Partitioner.Create(0, input.Length); 

     int N = input.Length; 
     double[] output = new double[N]; 

     Parallel.ForEach(aRange, (r) => 
      { 
       for (int i = r.Item1; i < r.Item2; i++) output[i] = (double)(input[i]); 
      }); 

     return output; 
    } 

    public unsafe static double[] Cast7(int[] input) 
    { 
     var aRange = Partitioner.Create(0, input.Length); 

     int N = input.Length; 
     double[] output = new double[N]; 

     Parallel.ForEach(aRange, (r) => 
     { 
      fixed (double* output_pinned = output) 
      { 
       double* outp = output_pinned + r.Item1; 

       fixed (int* input_pinned = input) 
       { 
        int* inp = input_pinned + r.Item1; 

        for (int i = r.Item1; i < r.Item2; i++, outp++, inp++) *outp = (double)(*inp); 
       } 
      } 
     }); 

     return output; 
    } 

    public unsafe static double[] Cast8(int[] input) 
    { 
     var result = (from m in input.AsParallel() select (double)m).ToArray(); 

     return result; 
    } 


    public static double[] Cast9(int[] input) 
    { 
     return (from m in input select (double)m).ToArray(); 
    } 

    public static double[] Cast10(int[] input) 
    { 
     return (from m in input.AsParallel() select (double)m).ToArray(); 
    } 

    public static double[] Cast11(int[] input) 
    { 
     return new List<double>(input.Select(p => (double)p)).ToArray(); 
    } 

    static int[] A = new int[100000]; 
    const int runs = 10000; 

    public static void StartTest() 
    { 
     TestMethod("1", Cast1); 
     TestMethod("2", Cast2); 
     TestMethod("3", Cast3); 
     TestMethod("4", Cast4); 
     TestMethod("5", Cast5); 
     TestMethod("6", Cast6); 
     TestMethod("7", Cast7); 
     TestMethod("8", Cast8); 
     TestMethod("9", Cast9); 
     TestMethod("10", Cast10); 
     TestMethod("11", Cast11); 
    } 

    static void TestMethod(string Name, CastMethod method) 
    { 
     var timer = Stopwatch.StartNew(); 

     for (int i = 0; i < runs; i++) { double[] res = method(A); } 

     timer.Stop(); 

     Console.WriteLine(String.Format("{0}: {1}ms", Name, timer.ElapsedMilliseconds)); 
    } 
} 

Grazie Martin

+2

Può fare un esempio specifico qui di cosa potrebbero essere 'InputType',' OutputType' e 'Tout'? –

+0

Ciao Marc, mi dispiace ho fatto un errore nel codice. Ho fatto un aggiornamento. – msedi

+0

lo fa in linq fa la differenza? (Non sono vicino all'ambiente di sviluppo ma questo è grosso modo il suo significato) var result = (da m in range.AsParallel() select (int) m) .ToArray(); – Peter

risposta

2

Hai provato questo?

public static TOut[] Cast<TOut,TIn>(TIn[] arr) { 
     return arr.Select(x => (TOut)Convert.ChangeType(x,typeof(TOut))).ToArray(); 
} 
+0

Sì, l'ho fatto. Ma per il mio scopo è di rallentare. Confrontando il tuo metodo con i miei metodi di puntatore è intorno a un fattore di 10 in termini di prestazioni. – msedi

+0

Non penso che ci sia un modo più veloce senza usare codice non sicuro. Se hai bisogno di prestazioni, il tuo approccio con i puntatori è il migliore –

+0

@ user1077243. Hai sicuramente ragione. Questo è quello che faccio attualmente, ma questo mi causa un sacco di mal di testa poiché C# non consente puntatori ai generici e devo fare molto per conto mio. – msedi

8

Non vi è alcuna conversione magica (quando si utilizzano generici, ecc.) Tra tipi numerici come questo; ci sono trucchi come Convert.ChangeType o dynamic, ma entrambi implicano una casella/unbox intermedia.

Personalmente, mi piacerebbe solo essere utilizzando:

float[] A = { 0.1f, 0.2f, 0.6f }; 
int[] B = Array.ConvertAll(A, x => (int)x); 

Questo alleggerisce la logica di conversione al compilatore (per usare la conversione corretta da float a int, senza intermediari o riflessione). Tuttavia, non è utilizzabile all'interno dei generici, ad esempio x => (OutputType)x non funzionerà.

+0

Ciao Marc, rispetto a tutti gli altri metodi è circa un fattore da 1,5 a 2 più lento di tutti gli altri metodi. Invierò un codice di test qui per verificare per voi tutti. – msedi

+0

@msedi stai dicendo che 'Array.ConvertAll' è più lento di' Convert.ChangeType'? Inoltre: di che dimensioni è il tuo array? –

+0

No, certo che no ;-) Ho pubblicato un codice di test. Dove puoi vedere i risultati. È interessante notare che c'è una differenza in VS o VS. Differenze in x86 e x64, e anche in Release in modalità Debug, ovviamente. – msedi

0

ho fatto tre esperimenti banali oltre un array di float con 38988 oggetti in esso (ho appena tagliato e incollato un gruppo di valori arbitrari più e più volte)

//_a = array of floats 


// pretty standard way of doing it 4ms 
_result = (from m in _a select (int)m).ToArray(); 

// I was rather disappointed with this 35ms 
_result = (from m in _a.AsParallel() select (int)m).ToArray(); 

// using a list rather surprised me 1ms 
_result = new List<int>(_a.Select(p => (int)p)).ToArray(); 

quindi non so come questi confronto con i tuoi test, ma direi che la selezione nell'elenco generico è piuttosto efficace.

EDIT:

Sto aggiungendo il mio codice. Devo mancare qualcosa perché ottengo risultati del tutto diversi dal mio esempio rispetto all'esecuzione del tuo esempio.

class Program 
{ 
    static void Main(string[] args) 
    { 

     using (var x = new ArrayCast()) 
     { 
      x.Run(); 
     } 

     using (var x = new ArrayCastList()) 
     { 
      x.Run(); 
     } 
     using (var x = new ArrayCastAsParallel()) 
     { 
      x.Run(); 
     } 

     while (Console.Read() != 'q') 
     { 
      ; // do nothing... 
     } 
    } 
} 

public abstract class Experiment : IAmATest, IDisposable 
{ 
    private Stopwatch _timer; 


    protected bool IgnoreAssert { get; set; } 

    #region IAmATest Members 

    public abstract void Arrange(); 
    public abstract void Act(); 
    public abstract void Assert(); 

    #endregion 

    public void Run() 
    { 
     _timer = Stopwatch.StartNew(); 
     Arrange(); 
     _timer.Stop(); 

     Console.WriteLine(String.Join(":", "Arrange", _timer.ElapsedMilliseconds)); 

     _timer = Stopwatch.StartNew(); 
     for (int i = 1; i < 1000; i++) 
     Act(); 
     _timer.Stop(); 

     Console.WriteLine(String.Join(":", "Act", _timer.ElapsedMilliseconds)); 

     if (IgnoreAssert) { return; } 

     _timer = Stopwatch.StartNew(); 
     Assert(); 
     _timer.Stop(); 

     Console.WriteLine(String.Join(":", "Assert", _timer.ElapsedMilliseconds)); 
    } 

    public abstract void Dispose(); 
} 

public class ArrayCast : Experiment 
{ 
    private int[] _a; 
    double[] _result; 

    public override void Arrange() 
    { 
     IgnoreAssert = true; 
     _a = new int[100000]; 
    } 

    public override void Act() 
    { 
      _result = (from m in _a select (double)m).ToArray(); 
    } 

    public override void Assert() { } 

    public override void Dispose() { _a = null; } 
} 

public class ArrayCastAsParallel : Experiment 
{ 
    private int[] _a; 
    double[] _result; 

    public override void Arrange() 
    { 
     IgnoreAssert = true; 
     _a = new int[100000]; 
    } 

    public override void Act() 
    { 
     _result = (from m in _a.AsParallel() select (double)m).ToArray(); 
    } 

    public override void Assert() { } 

    public override void Dispose() { _a = null; } 
} 

public class ArrayCastList : Experiment 
{ 
    private int[] _a; 
    double[] _result; 

    public override void Arrange() 
    { 
     IgnoreAssert = true; 
     _a = new int[100000]; 
    } 

    public override void Act() 
    { 
     var x = new List<double>(_a.Select(p => (double)p)); 
    } 

    public override void Assert() { } 

    public override void Dispose() { _a = null; } 
} 
+0

Ciao Peter, grazie per il tuo esempio. L'ho aggiunto al mio codice di test e ho fatto una corsa su tutti i casi. Sembra che tutti i test che hanno a che fare con Linq siano estremamente lenti. Potresti eseguire una prova con il codice di prova sulla tua macchina? Grazie Martin – msedi

1

Perché non si utilizza semplicemente Cast per gli elenchi, è LINQ Cast metodo System.Linq.Enumerable:

float[] A = { 0.1f, 0.2f, 0.6f }; 
int[] B = A.Cast(Of int).ToArray(); 

potete leggere qui: Enumerable.Cast(Of TResult) Method

+0

Esatto, ma l'utilizzo del metodo .Cast mi dà errori nei casi in cui un cast non è possibile, ad es. da float a int. Se voglio semplicemente scriverlo in questo modo, non funziona. – msedi

Problemi correlati