【问题标题】:C# Declaring local vs out of scope function variables and performance vs readability/robustnessC# 声明本地与超出范围的函数变量以及性能与可读性/健壮性
【发布时间】:2018-10-28 04:12:09
【问题描述】:

我一直想知道如何处理这个问题已经有一段时间了,但我找不到一个优雅的解决方案。我认为一些例子是理解问题的最简单方法。

假设我们在一个类中有这段代码,当调用它们的函数时,x 和 y 需要从 0 开始:

// here we have x as a local variable
private void functionX() {
    int x = 0;
    // ...
    // do stuff with x
    // ...
}

// here we have y as an out of function scope variable
int y;
private void functionY() {
    y = 0;
    // ...
    // do stuff with y
    // ...
}

public void update() 
{
    // this is slower because x gets a new instance every time functionX gets called    
    for (int i = 0; i < 100000; i++) {
        functionX();
    }

    // this is faster because y gets only one instance before the function ever gets called
    for (int i = 0; i < 100000; i++) {
        functionY();
    }
}

我已经测试了这段代码,并使用函数范围外的变量而不是使用局部变量产生了更好的性能(尽管在这个例子中不是很多,但仍然有性能提升)。缺点是您必须在函数范围外部声明函数变量才能获得这种性能提升,这会使您的代码更混乱且更容易出错。

这只是一个非常简单的示例,但是如果您有数千行代码,其中包含大量此类函数变量,并且将它们超出函数范围所带来的性能提升不容忽视,但您因拥有而造成的混乱所有这些超出范围的变量也不能被忽略吗?是否有解决此问题的方法,或者您必须在性能和可读性/稳健性之间做出选择?

ps.当您必须从它们所在的类构造多个对象时,在其函数中制作 x 或 y 静态变量也不起作用(您的所有对象都将具有x 和 y 表示整个程序运行时)

编辑:进一步简化代码

【问题讨论】:

  • 交换 functionX 和 functionY 循环的顺序,这样你首先调用 Y 并重新测试,我打赌你会看到 X 变得更快。在这种人为的情况下进行测试很难正确进行。
  • functionY 的性能应该更好的唯一原因是:y = 0 只有一个赋值;在functionX 中是两次分配,因为int x; 已经将零分配给x,然后您第二次手动分配它。还有一个。 y 变量与类一起编译,但 x 变量仅在首次使用时编译。在循环之前再调用一次functionXfunctionY,以提前编译函数。
  • 如果您有数千行代码,如FunctionY,那么您遇到的问题远不止性能差异那么大。
  • 我其实很喜欢微优化问题,但是这里没有什么可以优化的 :( 话虽如此,你在另一个方法的范围内创建的每个变量都将在堆栈上创建,并且将是一个分配,并且会占用早熟的 CPU 周期。但是,创建这些 Instance 字段是一个坏主意,您已经停止了并行此操作的任何机会。
  • I tested on debug mode initially 从不在调试模式下测试性能。这完全是浪费时间。

标签: c# performance optimization scope local-variables


【解决方案1】:

如果它适用于不复杂的变量,例如 int/string/double 等。除非您每秒执行 1.000.000 次,否则几乎没有差异。如果您认为使用 new Element() 它是一个不同的故事示例,您可以测试它是 C# 或任何其他语言中的 Vector3。 c#中的示例代码

// slowest
void functionX(){
    Vector3 newV3_0 = new Vector3();
}
// abit faster then X
Vector3 newV3_1;
void functionY(){
    newV3_1 = new Vector3();
}
// the fastest solution
Vector3 newV3_2 = Vector3.zero;
void functionZ(){
    //do things with vector;
}
// abit slower then Z
Vector3 newV3_3;
void functionQ(){
    newV3_3 = Vector3.zero;
}

如果您要分别循环它们中的每一个,例如 1.000.000 次 functionZ 将执行最快,因为内部没有任何操作,因为您在外部执行所有操作。但是这也有一个缺点,你需要记住不要在其他地方使用这个变量

【讨论】:

    【解决方案2】:

    “我已经测试了这段代码,并且使用函数范围外的变量而不是使用局部变量产生了更好的性能(尽管在这个例子中不是很多,但仍然有性能提升)。”我认为这需要性能咆哮: https://ericlippert.com/2012/12/17/performance-rant/ 虽然您完成了第 1 部分(希望能够给出有意义的结果),但 2-6 仍然适用于您的案例。

    我认为可读性和健壮性是最重要的。许多微优化(死代码检测,内部函数,添加或删除临时变量)可以留给 JiT。如果您确实需要这种差异,那么有 99% 的情况是您正在做Realtime Programming。尽管它很强大,但实时编程并不是 .NET Framework 的强项。仅仅拥有一个垃圾收集器通常是不合格的。

    【讨论】:

    • 我知道你从哪里来,也许你认为我不需要表演的原因是因为大多数人实际上并不需要,即使他们确实有表演,他们也只是认为他们需要增加它对于他们正在尝试做的事情绝对是微不足道的。我过于简化的代码也无济于事。事实上,我正在制作一个我的世界风格的游戏,你有无限的地形和理论上无限数量的 npc,所以是的,尤其是在我的情况下,性能为王,比其他任何事情都重要。
    • @Devez 你读过这篇文章吗?还有内存消耗。在使用局部变量时,内存使用只会在需要时使用;对于类成员,变量将一直存在于类中,因此它可以并且将导致更大的内存使用。它是可以接受的?当它超出标准范围时,大内存使用不会导致速度变慢吗?
    • @Julo 实际上我认为情况正好相反。我没有阅读所有文章,但是您阅读了我的代码吗?你认为创建一个局部变量的 10000 个实例只是为了将其设置为 0 比实例化一次并在每个函数调用开始时将其值设置为 0 更节省内存吗?我不这么认为。我的测试结果也不这么认为。第二个循环几乎是第一个循环的两倍。
    • @Devez Minecraft(与任何服务器游戏或模拟游戏一样)基于大规模并行化和分割工作。您以这种方式挤出性能的方法注定要失败。它还不够快。 wiki.vg/Chunk_Format
    猜你喜欢
    • 2014-11-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多