15

如果我有以下函数,它被认为是纯粹的,因为它没有副作用,并且在给定相同输入x的情况下总是会产生相同的结果。

public static int AddOne(int x) { return x + 1; }

据我了解,如果运行时了解功能纯度,它可以优化执行,从而不必重新计算返回值。

有没有办法在 C# 中实现这种运行时优化?而且我认为这种优化有一个名称。它叫什么?

编辑:显然,我的示例函数不会从这种优化中获得很多好处。给出这个例子是为了表达我心中的纯洁类型,而不是现实世界的例子。

4

8 回答 8

26

正如其他人所指出的,如果您想节省重新计算已计算结果的成本,那么您可以记忆该函数。这以增加的内存使用量换取了速度的提高——如果您怀疑如果缓存无限制地增长,您可能会耗尽内存,请记得偶尔清除缓存。

然而,除了记忆它们的结果之外,还可以对纯函数执行其他优化。例如,没有副作用的纯函数通常可以安全地调用其他线程。使用大量纯函数的算法通常可以并行化以利用多个内核。

随着大规模多核机器变得越来越便宜和越来越普遍,这一领域将变得越来越重要。我们对 C# 语言有一个长期的研究目标,以找出某种方法来利用语言、编译器和运行时中的纯函数(以及不纯但“孤立”的函数)的强大功能。但这样做涉及许多难题,工业界或学术界对最佳方法几乎没有共识。高层人士正在考虑它,但不要指望很快就会有任何重大成果。

于 2009-09-01T15:24:10.003 回答
8

如果计算成本很高,您可以将结果缓存在字典中吗?

    static Dictionary<int, int> cache = new Dictionary<int, int>();
    public static int AddOne(int x)
    {
        int result;
        if(!cache.TryGetValue(x, out result))
        {
            result = x + 1;
            cache[x] = result;
        }
        return result;
    }

当然,在这种情况下,字典查找比添加更昂贵:)

Wes Dyer 在这里解释了另一种更酷的功能记忆方法:http: //blogs.msdn.com/wesdyer/archive/2007/01/26/function-memoization.aspx - 如果你做了很多这种缓存,那么他的 Memoize 功能可能会为您节省大量代码......

于 2009-09-01T15:15:26.387 回答
2

我认为您正在寻找功能性记忆

于 2009-09-01T15:15:44.480 回答
2

您所追求的技术是memoization:缓存执行结果,将传入函数的参数键入到数组或字典中。运行时并不倾向于自动应用它,尽管在某些情况下它们会这样做。C# 和 .NET 都不会自动应用 memoization。您可以自己实现 memoization - 这很容易 - 但这样做通常仅对较慢的纯函数有用,在这些函数中您倾向于重复计算并且您有足够的内存。

于 2009-09-01T15:16:37.367 回答
1

这可能会被编译器内联(又名内联扩展)......

只需确保使用“优化代码”标志集编译代码(在 VS 中:项目属性/构建选项卡/优化代码)


您可以做的另一件事是缓存结果(又名memoization)。但是,由于您的查找逻辑,初始性能会受到巨大影响,因此这仅对慢速函数有意义(即不是 int 加法)。

还有内存影响,但这可以通过巧妙地使用弱引用来管理。


据我了解,如果运行时了解功能纯度,它可以优化执行,从而不必重新计算返回值。

在您的示例中,除非 x 在编译时已知,否则运行时将必须计算结果。在这种情况下,您的代码将通过使用常量折叠进一步优化

于 2009-09-01T15:12:47.497 回答
0

编译器可以通过内联(在调用点用该函数的主体替换函数调用)和常量传播(用该表达式的结果替换没有自由变量的表达式)的组合来优化此函数。例如,在这段代码中:

AddOne(5);

AddOne 可以内联:

5 + 1;

常量传播可以简化表达式:

6;

死代码消除可以进一步简化这个表达式,但这只是一个例子)。

知道AddOne()没有副作用也可能使编译器能够执行公共子表达式消除,以便:

AddOne(3) + AddOne(3)

可以转化为:

int x = AddOne(3);
x + x;

或通过强度降低,甚至:

2*AddOne(3);

无法命令 c# JIT 编译器执行这些优化;它自行决定优化。但它非常聪明,您应该可以放心地依靠它来执行这些类型的转换,而无需您的干预。

于 2009-09-01T15:16:35.853 回答
0

编译器怎么能做到这一点?它如何知道在运行时将传入 x 的哪些值?

和回复:其他提到内联的答案......我的理解是,内联(作为一种优化)对于只使用一次(或只使用几次......)的小函数是有保证的,而不是因为它们没有副作用。 ..

于 2009-09-01T15:16:48.100 回答
0

另一种选择是使用 fody 插件https://github.com/Dresel/MethodCache 您可以装饰应该缓存的方法。使用它时,您当然应该考虑其他答案中提到的所有评论。

于 2015-02-03T07:37:28.003 回答