0

我提前为这个问题的长度道歉......这有点牵扯。我正在编写一个非常简单的“加权和”运算;取 n 个图像,将每个图像乘以特定的乘数并将它们相加成输出图像(通过迭代每个像素)。当图像数量不变时,我可以将逻辑硬编码到一次迭代中,但是,我希望使该方法足够灵活以处理可变数量的图像。我无法想出一个同样“高效”的方法来实现这一点,例如,当输入数量未知时,没有额外的内部循环。这是我的情况:

var rnd = new Random();

//Pixels in input and output images
const int x = 1000000;

//An output composite image
var pixelmap = new int[x];

var tStart = DateTime.Now;

//Known number of inputs
int knownNumberOfInputs = 3;

//Weights to apply to each pixel of the input images
//multipliers[0] applies to all pixels of inputA,
//multipliers[1] applies to all pixels of inputB etc.
var multipliers = new byte[3];
rnd.NextBytes(multipliers);

/* situation 1 
* - I know how many input images
* - Arrays are independent */

//3 (knownNumberOfInputs) input images (we'll use random numbers for filler)
var inputA = new byte[x];
rnd.NextBytes(inputA);
var inputB = new byte[x];
rnd.NextBytes(inputB);
var inputC = new byte[x];
rnd.NextBytes(inputC);

//I can iterate through each pixel of each input image, multiply and sum for pixelmap value.
//Without a nested loop
for (var i = 0; i < x; i++)
{
    pixelmap[i] = (
        (inputA[i]*multipliers[0]) +
        (inputB[i]*multipliers[1]) +
        (inputC[i]*multipliers[2])
                        );
}

Console.WriteLine("Operation took " + DateTime.Now.Subtract(tStart).TotalMilliseconds + " ms");
// Operation took 39 ms

tStart = DateTime.Now;

/* situation 2
* unknown number of inputs
* inputs are contained within jagged array */

/* Caveat - multipliers.Length == inputs.Length */

//var unknownNumberOfInputs = rnd.Next(1, 10);
var unknownNumberOfInputs = 3; //Just happens to be the same number (for performance comparisons)

multipliers = new byte[unknownNumberOfInputs];
rnd.NextBytes(multipliers);

//Jagged array to contain input images
var inputs = new byte[unknownNumberOfInputs][];

//Load unknownNumberOfInputs of input images into jagged array
for (var i = 0; i < unknownNumberOfInputs; i++)
{
    inputs[i] = new byte[x];
    rnd.NextBytes(inputs[i]);
}

// I cannot iterate through each pixel of each input image
// Inner nested loop
for (var i = 0; i < x; i++)
{
    for (var t=0;t<multipliers.Length;t++)
    {
        pixelmap[i] += (inputs[t][i] * multipliers[t]);
    }
}

Console.WriteLine("Operation took " + DateTime.Now.Subtract(tStart).TotalMilliseconds + " ms");
//Operation took 54 ms

//How can I get rid of the inner nested loop and gain the performance of LoopA?
//Or is that the cost of not knowing?

大起大落

更多信息

  • 像素图将进入 Silverlight 中的 WriteableBitmap - 一旦构建,它将以一维数组作为像素源(因为高度/宽度被传递给构造函数)
  • 每个输入图像都有一个特定的乘数,例如将输入 1 的所有像素乘以 2,将输入 2 的所有像素乘以 3,等等。
  • 输入永远不会超过 20 个。
4

4 回答 4

1

我建议您创建一个表示您的计算的表达式。然后编译该表达式。

您的表达式将是一个 lambda。三个输入的示例:

void (byte[] inputA, byte[] inputB, byte[] inputC) {
for (var i = 0; i < x; i++)
{
    pixelmap[i] = (
        (inputA[i]*multipliers0) +
        (inputB[i]*multipliers1) +
        (inputC[i]*multipliers1)
                        );
}
}

使用 .NET 4,您可以将 for 循环用作表达式(不在 .NET 2 中)。

这听起来很难,但实际上很容易做到这一点。

澄清一下:您将在运行时编译一个专用于恒定数量输入的函数。

您甚至可以玩一些技巧,例如将循环展开 2 或 4 次。您还可以像在示例中那样将乘数内联为常量。这将比嵌套循环快得多。

请注意,循环位于表达式树内部,而不是围绕它。这意味着您只有一个委托调用(以及可重用编译结果)的开销。

以下是一些示例代码,可帮助您入门:

int inputCount = ...;
var paramExpressions = GenerateArray(inputCount, i => Expression.Parameter(typeof(byte[]), "input" + i);

var summands = GenerateArray(inputCount, i => Expression.Mul(/* inputA[i] HERE */, Expression.Constant(multipliers[i]));

var sum = summands.Aggregate((a,b) => Expression.Add(a,b));

var assignment = /* assign sum to pixelmap[i] here */;

var loop = /* build a loop. ask a new question to find out how to do this, or use google */

var lambda = Expression.Lambda(paramExpressions, loop);

var delegate = lambda.Compile();

//you are done compiling. now invoke:

delegate.DynamicInvoke(arrayOfInputs); //send an object of type byte[][] into the lambda

就是这样。你需要填补空白。

于 2012-01-25T19:18:43.227 回答
1

您可以通过三种方式提高此代码的性能:

  1. 切换ti循环。这样,您将使用相同的 2 个大数组,并且可以应用第 2 项:
  2. 使用临时变量消除内部循环中的数组访问。
  3. 使用一种称为“循环展开”的技术:以 3 个为一组进行计算,直到剩下的输入少于 3 个。然后再做一个循环。

这就是这一切的样子:

int t = 0;
for (; t < multipliers.Length - 2; t += 3) {
    var input1 = inputs[t];
    var input2 = inputs[t+1];
    var input3 = inputs[t+2];
    var multiplier1 = multipliers[t];
    var multiplier2 = multipliers[t+1];
    var multiplier3 = multipliers[t+2];
    if (t == 0) {
        for (var i = 0; i < x; i++)
            pixelmap[i] = input1[i] * multiplier1
                + input2[i] * multiplier2
                + input3[i] * multiplier3;
    } else {
        for (var i = 0; i < x; i++)
            pixelmap[i] += input1[i] * multiplier1
                + input2[i] * multiplier2
                + input3[i] * multiplier3;
    }
}
if (multipliers.Length < 3)
    Array.Clear(pixelmap, 0, pixelmap.Length);
for (; t < multipliers.Length; t++) {
    var input = inputs[t];
    var multiplier = multipliers[t];
    for (var i = 0; i < x; i++)
        pixelmap[i] += input[i] * multiplier;
}

我还会对您计算结果的方式进行一些更改:

  1. 看起来您正在 Visual Studio 中运行基准测试,可能处于调试模式。确保在发布模式下在 Visual Studio 之外运行基准测试。
  2. 只对您感兴趣的代码进行基准测试。留下创建测试数据的代码。
  3. 秒表类非常适合用于计时目的,尤其是对于非常短的时间跨度。
于 2012-01-25T19:21:08.230 回答
0

您应该尝试交换内部和外部循环。

您的像素图可能适合 Cpu 缓存,然后多次写入它就不会受到太大的伤害。

然后,您可以展开迭代像素以获得最佳性能的内部循环。请务必在调试器之外测试发布版本以获得正确的时间。

如果您仍然不满意,您可以一次计算一条图像扫描线。

于 2012-01-25T19:08:13.810 回答
0

这是另一个答案:使用 T4 模板为 1 到 20 个输入生成所有可能的函数作为编译时间。这不像我之前的答案那么酷,但也可以正常工作。

于 2012-01-25T19:26:41.963 回答