C # универсальные массивы и математические операции над ним - PullRequest
3 голосов
/ 11 апреля 2010

В настоящее время я участвую в проекте, где у меня очень большие объемы изображений. Эти тома должны обрабатываться очень быстро (сложение, вычитание, пороговое значение и т. Д.). Кроме того, большая часть тома настолько велика, что она не помещается в память системы. По этой причине я создал абстрактный класс тома (VoxelVolume), который содержит данные тома и изображения и перегружает операторы, чтобы можно было выполнять обычные математические операции над томами. Тем самым открылись еще два вопроса, которые я добавлю в stackoverflow в два дополнительных потока.

Вот мой первый вопрос. Мой том реализован таким образом, что он может содержать только данные массива с плавающей запятой, но большинство содержащихся данных получены из источника изображения UInt16. Только операции с томом могут создавать образы массивов с плавающей точкой.

Когда я начал реализовывать такой том, класс выглядел следующим образом:

public abstract class VoxelVolume<T> 
{
...
}

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

public abstract class VoxelVolume<T>
{
...
    public static VoxelVolume<T> Import<T>(param string[] files) 
    {
    } 
}

также добавление двух операторов перегрузки будет более сложным:

...
public static VoxelVolume<T> operator+(VoxelVolume<T> A, VoxelVolume<T> B)
{
...
}    

Давайте предположим, что я могу преодолеть проблемы, описанные выше, тем не менее, у меня есть различные типы массивов, которые содержат данные изображения. Так как я установил свой тип в томах для плавания, это не проблема, и я могу сделать небезопасную операцию при добавлении содержимого двух массивов томов изображений. Я прочитал несколько веток здесь и осмотрел сеть, но не нашел по-настоящему хорошего объяснения того, что делать, когда я хочу быстро добавить два массива разных типов. К сожалению, каждая математическая операция над обобщениями невозможна, поскольку C # не может рассчитать размер базового типа данных. Конечно, можно обойти эту проблему, используя C ++ / CLR, но в настоящее время все, что я сделал до сих пор, работает в 32-битном и 64-битном режимах без необходимости что-либо делать. Переключение на C ++ / CLR показалось мне (приятно поправить меня, если я ошибаюсь), что я привязан к определенной платформе (32-битной) и мне нужно скомпилировать две сборки, когда я позволяю приложению работать на другой платформе (64-битной). Это правда?

Итак, коротко спросили: как можно быстро добавить два массива двух разных типов. Правда ли, что разработчики C # не думали об этом. Переключение на другой язык (C # -> C ++) не представляется возможным.

Я понимаю, что просто выполняя эту операцию

float []A = new float[]{1,2,3};  
byte  []B = new byte[]{1,2,3};

float []C = A+B;

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

public static class ArrayExt
{
    public static unsafe TResult[] Add<T1, T2, TResult>(T1 []A, T2 []B)
    {
       // Assume the length of both arrays is equal
       TResult[] result = new TResult[A.Length];

       GCHandle h1 = GCHandle.Alloc (A, Pinned);
       GCHandle h2 = GCHandle.Alloc (B, Pinned);
       GCHandle hR = GCHandle.Alloc (C, Pinned);

       void *ptrA = h1.ToPointer();
       void *ptrB = h2.ToPointer();
       void *ptrR = hR.ToPointer();

       for (int i=0; i<A.Length; i++)
       {
          *((TResult *)ptrR) = (TResult *)((T1)*ptrA + (T2)*ptrB));
       }

       h1.Free();
       h2.Free();
       hR.Free();

       return result;
    }
}

Пожалуйста, извините, если приведенный выше код не совсем корректен, я написал его без использования редактора C #. Возможно ли такое решение, показанное выше? Пожалуйста, не стесняйтесь спрашивать, сделал ли я ошибку или описал некоторые вещи не полностью.

Спасибо за вашу помощь
Martin

Ответы [ 4 ]

1 голос
/ 11 апреля 2010

Если у вас есть только несколько типов, таких как float и UInt32, предоставьте все необходимые функции преобразования, например, от VoxelVolume<UInt32> до VoxelVolume<float> и выполните математические операции на VoxelVolume<float>. Это должно быть достаточно быстро для большинства практических случаев. Вы можете даже предоставить общую функцию преобразования из VoxelVolume<T1> в VoxelVolume<T2> (если T1 конвертируется в T2). С другой стороны, если вам действительно нужно

public static VoxelVolume<T2> operator+(VoxelVolume<T1> A,VoxelVolume<T2> B)

с преобразованием типов из T1 в T2 для каждого элемента массива, что мешает вам писать такие операторы?

1 голос
/ 11 апреля 2010

Импорт, будучи членом универсального класса, вероятно, не обязательно должен быть сам по себе универсальным. Если это так, вам определенно не следует использовать одно и то же имя T как для универсального параметра класса, так и для универсального параметра функции.

Что вы, вероятно, ищете, это Универсальные операторы Марка Гравелла

Что касается ваших вопросов о C ++ / CLI, да, это может помочь, если вы используете шаблоны вместо шаблонов, потому что тогда все возможные значения для typename T контролируются во время компиляции, и компилятор ищет операторы для каждого. Кроме того, вы можете использовать /clr:pure или /clr:safe, в этом случае ваш код будет MSIL, и будет работать на AnyCPU точно так же, как C #.

1 голос
/ 11 апреля 2010

Это кажется (сложной) версией "почему у нас нет INumeric интерфейса".

Краткий ответ на последний вопрос: нет, переход к небезопасным указателям не является решением, компилятор все еще не может определить + в ((T1)*ptrA + (T2)*ptrB)).

0 голосов
/ 11 апреля 2010

Правда, я не читал весь вопрос (это слишком долго), но:

  1. VoxelVolume<T> where T : ISummand ... T a; a.Add(b)
  2. static float Sum (this VoxelVolume<float> self, VoxelVolume<float> other) {...}
  3. Чтобы добавить float к байту в любом значимом смысле, вы должны преобразовать байт в float. Так что конвертируйте массив байтов в массив чисел с плавающей точкой, а затем добавляйте их, вы только потеряете часть памяти.
...