【问题标题】:Design approach: Overloading vs Switch?设计方法:重载与切换?
【发布时间】:2011-11-27 20:17:09
【问题描述】:

关于封装设计中的性能和可扩展性,是否最好:

  1. …“重载”函数名称(让 Mathematica 根据模式/条件/测试以及系统排序定义的方式来整理要使用的版本)?
  2. ... 还是使用 Switch[](或类似命令)构造单个函数来直接评估?

Mathematica 的表现力经常让我对诸如此类的愚蠢 (?) 问题感到困惑。

【问题讨论】:

  • 我觉得您应该为您的问题添加更多细节。在这一点上它非常广泛。
  • 不能说我不同意您的评估。我最初写的更多,但意识到我正在重新整理我们大多数人从手册和个人经验中了解的基础知识。如果可以的话,我会尽力澄清——但在大熊游戏之后。 :)
  • 我还写了一个更长的答案,然后意识到我的例子很平庸,所以我保持原样。享受游戏。

标签: wolfram-mathematica package


【解决方案1】:

这是一个宽泛的问题,但我会借此机会给出一个宽泛的答案......

我主张人们应该接受编程语言的主要范式,而不是试图与之抗争或编写遵循另一种语言习语的代码。 Mathematica 是围绕模式匹配的概念构建的,因此,恕我直言,在尝试表达自己时,我们应该始终首先考虑模式匹配。按照这个原则,我更倾向于定义而不是Switch

在性能问题上,在比较 Mathematica 构造时,我对越来越强调微基准越来越感到烦恼。虽然了解与构造相关的成本很有价值,但我们应该注意 Knuth(或者是 Hoare?):“我们应该忘记小的效率,比如大约 97% 的时间:过早的优化是万恶之源”。 “邪恶”是在程序中失去可读性,为了提高效率,使用一些晦涩或间接的方法来实现效果。这是我的绩效清单:

  1. 性能有问题吗?如果没有,请跳过检查表的其余部分。

  2. 性能瓶颈在哪里?探查器在这里有所帮助,但通常可以通过检查或一些打印语句轻松找到瓶颈。那么……

  3. 算法效率低吗?非常常见:是否存在可以通过索引方案线性化或帮助解决的双重嵌套循环?

  4. 好的,算法很好,所以我想是时候进行微基准测试了。

我不知道我对 Mathematica 的使用是否不够雄心勃勃,但大多数时候我都没有通过第 1 步。然后#3 抓住了其余的大部分。在 Mathematica 中,我发现我通常只是欣喜若狂,因为我可以用少量代码执行一些雄心勃勃的任务——总体性能通常不会被考虑在内。

呃-哦,我最好把肥皂盒收起来。对不起。

【讨论】:

  • 关于使用自然范式的一个很好的观点。关于您对 mma 的使用可能不够雄心勃勃,您对它的使用很可能过于复杂。至少在我的情况下,我主要使用 mma 来做一些事情:构造非常大的稀疏矩阵,获得特征值和向量,用它们做一些简单的事情。通常,最耗时的部分是构建矩阵,而不是求解特征系统。在那种情况下,微基准测试(或只是 Compile 到 C)是 C 之前的最后希望。对于更复杂的使用,我完全同意你的看法。
  • 除非我记错了,否则我从来没有祝贺你获得 10K 代表......恭喜!
【解决方案2】:

除了非常简单的情况,我更喜欢使用具有多个定义的函数而不是Switch。造成这种情况的原因有三个:

  1. 我发现只要函数命名得当,代码更容易阅读。
  2. 对于失败/错误情况,设置适当的默认值并再次调用该函数更容易。
  3. 如果您使用函数,则可以在计算结果时使用命名模式。

编辑

以下是为 Sjoerd 创建的 #2 示例:

createNColors[fn_, Automatic, n_] := Table[Hue[i/n], {i, n}]

createNColors[fn_, colors_List, n_] := PadRight[colors, n, colors]

createNColors[fn_, color:(Hue | RGBColor | CMYKColor | GrayLevel)[__], n_] := 
    Table[color, {n}]

createNColors[fn_, color_, n_] := (
    Message[fn::"color", HoldForm[color]]; 
    createNColors[fn, Automatic, n]
    )  

它可用于为某些选项生成一组 n 种颜色。

【讨论】:

  • 如果你能提供一个很好的例子来说明你的第二点,你会得到 +1 ;-)
  • 谢谢,很有启发!最后一个定义,不应该也适用于createNColors 而不是createNStyle,以便您可以适当地处理createNColors[fn, CIELab[1, 0.5, 0.25], 6] 之类的调用?
  • @Sjoerd:感谢您发现错误。我在最后一分钟不完美地重命名了东西。
【解决方案3】:

要回答您问题的性能部分,请考虑以下两个重载示例和使用Switch[]

switchFunc[a_] :=  Switch[a, _String, 5, _Integer, var, _Symbol, "string"] 

overloadFunc[a_String]  := 5;
overloadFunc[a_Integer] := var;
overloadFunc[a_Symbol]  := "string";

这非常简化,但足以证明性能差异

In[1]  := Timing@Nest[switchFunc, x, 1000000]
Out[1] := {3.435, "string"}

In[2]  := Timing@Nest[overloadFunc, x, 1000000]
Out[2] := {0.754, "string"}

但是,如果你打算基于条件测试重载你的函数,性能会比Switch[]差:

switchFunc2[a_] := Switch[a < 5, True, 6, False, 4];

overloadFunc2[a_ /; a < 5] := 6;
overloadFunc2[a_ /; a > 5] := 4;
overloadFunc2[a_] := a;

In[3]  := Timing@Nest[switchFunc2, 4, 1000000]
Out[3] := {2.63146, 4}

In[4]  := Timing@Nest[overloadFunc2, 6, 1000000]
Out[4] := {4.349, 6}

编辑:此答案中的时间安排是在 OS X 10.7.2 上使用 Mathematica 8.0.1 制作的。有关上述顺序颠倒的一些额外结果,请参见 Mr.Wizard 的回答。尽管如此,我认为对函数参数进行逻辑模式检查是一个坏主意。

从设计的角度来看,我个人的经验是Switch[] 和它的同类很糟糕,因为它们很难阅读并且速度很慢。但是,我也认为根据参数的类型让相同的函数执行不同的操作通常是糟糕的设计,并且会使您的代码更难遵循(即使它可能更容易阅读)。

【讨论】:

    【解决方案4】:

    你的问题写得很模糊,对“重载”有不同的解释,这会改变我的答案。但是,如果您要针对不同类型(头)和参数模式重载自己的函数,那么一定要利用 Mathematica 紧密集成的模式匹配。


    为了提供一个实际的例子,我将使用this solution of mine。供参考:

    f[k_, {}, c__] := If[Plus[c] == k, {{c}}, {}]
    
    f[k_, {x_, r___}, c___] := Join @@ (f[k, {r}, c, #] & /@ Range[0, Min[x, k - Plus[c]]])
    

    如果我在没有模式匹配的情况下重写f 并将其命名为g

    g = Function[{k, L, c},
          If[L === {},
             If[Tr@c == k, {c}, {}],
             Join @@ (g[k, Rest@L, Append[c, #]] & /@ Range[0, Min[First@L, k - Tr@c]])
          ]
        ];
    

    感觉这个不太清楚,写起来肯定不太方便。我必须使用显式的RestFirst 函数,并且我必须引入Append,因为我无法容纳可变数量的参数。这也需要使用第三个虚拟参数:{}

    时间表明原始形式也相当快:

    f[12, {1, 5, 8, 10, 9, 9, 4, 10, 8}]; // Timing
    g[12, {1, 5, 8, 10, 9, 9, 4, 10, 8}, {}]; // Timing
    
    {0.951, 空}
    {1.576, 空}

    对于 Timo 的回答,我觉得分享我的计时结果很有价值,因为它们与他的不同。 (我在 Windows 7 上使用 Mathematica 7。)此外,我相信他将 DownValues 版本复杂化,超出了 Switch 版本的功能。

    首先,我对他所写函数的时间安排,但使用了一系列值:

    Array[switchFunc2, 1*^6]; // Timing
    Array[overloadFunc2, 1*^6]; // Timing
    
    {1.014, 空}
    {0.749, 空}

    因此,即使按照编写方式,DownValues 函数对我来说也更快。但不需要第二个条件:

    ClearAll[overloadFunc2]
    
    overloadFunc2[a_ /; a < 5] := 6;
    overloadFunc2[a_] := 4;
    
    Array[overloadFunc2, 1*^6]; // Timing
    
    {0.546, 空}

    当然,对于这么简单的功能,也可以使用If

    ifFunc[a_] := If[a < 5, 6, 4]
    
    Array[ifFunc, 1*^6]; // Timing
    
    {0.593, 空}

    如果这是写成 Mathematica 在 Array 中编译的纯函数:

    ClearAll[ifFunc]
    ifFunc = If[# < 5, 6, 4] &;
    
    Array[ifFunc, 1*^6]; // Timing
    
    {0.031, 空}

    【讨论】:

    • 这让我想起了我喜欢函数的第三个原因——能够命名模式并在以后重用它们。
    • 很好地检查了时间!关于过载过多,我认为对于相同的功能,我的两个 DownValues 都需要。想想overloadFunc2["string"]switchFunc2["string"]。当然,我的示例非常简单,任何现实世界的应用程序都应该自行测试。
    • @Timo 我很困惑:*Func2 应该处理字符串吗?怎么样?
    • @Mr.W 不,他们不是,这就是重点。使用您的定义 overloadFunc2["string"] -&gt; 4switchFunc2["string"] 不会评估。吹毛求疵,我知道 ;-)。
    • 还有:expamnles ?说真的,什么时候会自动更正?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多