在C#中应用DeMorgan定理在条件语句中手动优化布尔表达式(例如条件)是否有用

问题描述 投票:10回答:15

回到我在C和C ++中完成大部分工作的那一天,当然,我会手动应用deMorgan's theorem来优化任何非平凡的布尔表达式。

在C#中执行此操作是有用的还是优化程序使这不必要?

c# optimization compiler-construction boolean-logic
15个回答
31
投票

在处理器上这么快,重新排列布尔表达式几乎不可能在速度上产生任何实际差异。 C#编译器非常智能,它也会优化它。优化可读性和清晰度!


2
投票

在存在短路评估的情况下,DeMorgan本身可能完全无关紧要。

return !(exp1 || exp2);
return !exp1 && !exp2;

得到编译

if(   exp1 ) return !(true); else return !(exp2);
if(!(!exp1)) return   false; else return !(exp2);

随着nots取消和常数折叠,这些是相同的。

更重要的案例是评估顺序;在表达式的前面放置可能引发短路的吱吱声。编译器无法为您优化这一点,因为它很难检测副作用等语义问题,或者后面的表达式是否基于先前的表达式做出假设:

return validState() && checkAssumuingValidState();

1
投票

我同意可读性和可维护性这些日子在优化布尔表达式时最重要的一般性陈述。因此,德摩根定理一般非常有用。

这条规则有一个例外。如果布尔表达式改变了Demorgan的定理优化表达式,则可能更难以维护。考虑一个具有多个输入的表达式,这些输入已被优化为仅显示一些布尔条件。对所需布尔逻辑的一个更改可能会强制某人再次列出所有可能的布尔组合,然后重新优化。如果表达式保留为未经优化的格式,则更改将花费更少的步骤来完成。

更多来自轶事的观点,我想知道是否教育团队关于DeMorgan的Theorem和Karnaugh Maps等会减少不必要/低效的布尔表达式。也许如果有人对这些方法有很好的理解,他/她就会倾向于产生更好的表达方式。例如,我最近在我维护的软件代码中遇到了这个布尔表达式:

if ({boolean variable} != true && false)

1
投票

考虑到逻辑表达式评估的短路规则,C#优化器实际上不能做太多。所以应用DeMorgan定律不会做太多,除非它允许你看到其他有用的重构(当然它可以帮助你的代码更清晰)。

但是在某些情况下,您可以通过其他类型的表达式优化来提高性能。例如,应该交换这些条件

if ( costly_boolean_function() && cheap_often_false_boolean_function() )

SQL查询优化器当然是这样做的,因为SQL没有短路。查询优化器将积极地重新排列连接WHERE子句谓词(形式为c1 AND c2 AND ... cn),以便将最便宜的条件放在首位,因为它们可以评估为false并且不需要评估更昂贵的条件。


0
投票

首先处理可维护性和high-level optimization

然后处理低级优化。


0
投票

De Morgan定律可用于将其降低到正常形式,例如析取正规形式(DNF)或合取正常形式(CNF)。基本上这意味着它也是

DNF:(a和b和c)OR(e和f和g)......

要么

CNF:(a或b或c)AND(e或f或g)....

你可以把NOT放在最低级别。

我同意之前的海报,你应该优化可读性和理解力。


0
投票

对于我们所有非CS专业:

wikipedia on De Morgan's laws

De Morgan的定律是通过否定将逻辑运算符“和”与“或”相互关联的规则,即:

NOT(P OR Q)=(NOT P)AND(NOT Q) NOT(P和Q)=(不是P)或(不是Q)


8
投票

您的第一个目标应该是优化这些语句,以便开发人员理解和易于维护。

DeMorgan的定理可以成为一个有用的工具。


7
投票

JIT中的优化,以其当前的形式,并没有(从我读过的内容)为您优化。如果您需要优化它,您仍需要考虑这一点。

话虽如此,这是一个相当小的微优化。一般来说,我更喜欢以更具表现力的形式编写“非平凡的布尔表达式”,以便更容易理解。对我来说,这比应用deMorgan定理得到的任何非常小的优化更有价值。


7
投票

我相信这个问题的正确答案是编译器没有(通常)优化布尔评估,仅仅是因为逻辑短路,例如:

if (GetFlagA() || GetFlagB())
{
   ...do something
}

如果调用GetFlagA可以修改GetFlagB所依赖的东西,那么评估的顺序是真正重要的(授予这是非常糟糕的代码实践,但这是另一个不同主题的主题。)这里的问题是逻辑短路,如果GetFlagA运行并且返回true,GetFlagB将永远不会运行,如此处所示,GetFlagB的结果对于语句的评估无关紧要。

A | B | =

F | F | F

F | T | Ť

T | F |无论B的返回值如何都为真。

T | T |无论B的返回值如何都为真。

总而言之,询问你是否可以通过使用Demorgan或其他任何东西进行优化,就像其他计算机科学和软件工程一样。 “这取决于。”如果您使用的是非功能性评估,则可能会进行优化。老实说,你在一个疯狂的平台上谈论一些操作,你最好花时间写文档。

我希望这有帮助。


3
投票

唯一一次你应该重新排列,布尔代数或demorgan是在逻辑太复杂而不能用另一种方式做的时候。如果它不是太复杂,请保持其可读性。有一种简化逻辑的例子。

有时当逻辑很棘手时,我需要创建一个Karnaugh Map来简化逻辑,直到我甚至可以写下来的东西。通常,使用K-Maps可以帮助您提供更简洁的表达逻辑的方法。结果可能或可能没有意义,但它将是等效的。

而且我还会说DeMorgan本身并不是一个会产生影响的优化,如果超过一半的条款是否定的(NOT),你最多会获得删除一些NOT的性能,这是一个单一的每个CPU的指令。在最坏的情况下,你可以添加尽可能多的NOT,如果你不应该使用DeMorgan,你会获得比你最初的更多的NOT。

如果您要优化逻辑,请使用一些布尔代数或我个人最喜欢的K-Maps来减少术语数量(如果可能)。不要只是移动布尔运算符,这很愚蠢。


3
投票

我想编译器已经这样做了。您可以通过Reflector进行测试并查看已编译的IL。

优化可读性和可维护性。问问自己,您是否会理解一年中的聪明优化,如果您认为,代码可以使用一些注释,使代码自我记录。


3
投票

由于布尔表达式求值使用快捷语义,因此您可以将更便宜的子表达式移动到前面:

if (CountAllFilesOnDrive('C:\') > 7 && useFileScan) { ... }

即使不需要,也会在评估表达式的任何时候运行昂贵的调用。如果useFileScan为false,则转过该语句会跳过文件检查:

if (useFileScan && CountAllFilesOnDrive('C:\') > 7) { ... }

DeMorgan可能会帮助你将“早期退出”转移到前线,从而获得更好的平均表现。

请注意,由于保证了从左到右的评估,优化器没有太多自由来修改表达式。


2
投票

考虑可读性和维护。如果你有一组相当复杂的难以阅读的布尔表达式,那么DeMorgan的theorm可能是一种很好的方法,可以将表达式简化为更易于阅读/维护的东西,但仍然有效/与原始表达式保持一致。

另一方面,如果更简洁的表达更容易阅读并减少表达,而在逻辑上等同,则使其更难以理解,保持原样。


2
投票

在我能想到的几乎所有实际情况中,布尔运算符的排列对整体性能没有明显影响。如果您的程序等待数据库,网络等,它将花费的时间远远超过那些微小的操作。如果你编写一个真正有所作为的程序,最好跳过C#并使用C ++代替。

© www.soinside.com 2019 - 2024. All rights reserved.