相关疑难解决方法(0)

ES6尾递归优化堆栈溢出

阅读了Rauschmayer博士对es6中递归尾调用优化的描述之后,我一直试图重新创建他详细说明的递归因子函数的"零堆栈"执行.

使用Chrome调试器在堆栈帧之间切换,我发现尾部优化没有发生,并且正在为每次递归创建堆栈帧.

我也尝试通过在没有调试器的情况下调用函数来测试优化,而是传递100000给阶乘函数.这会引发"最大堆栈"错误,这意味着它实际上并未进行优化.

这是我的代码:

const factorial = (n, acc = 1) => n <= 1 ? acc : factorial(n - 1, n * acc)
console.log( factorial(100000) )
Run Code Online (Sandbox Code Playgroud)

结果:

Uncaught RangeError: Maximum call stack size exceeded
Run Code Online (Sandbox Code Playgroud)

javascript stack-overflow optimization recursion ecmascript-6

32
推荐指数
2
解决办法
7413
查看次数

为什么在JavaScript中使用回调,它的优点是什么?

有人可以解释一下,为什么我们在JavaScript中使用回调?我找到了示例,但它们可以通过使用普通函数来实现.使用它有什么好处?我得到了"如何"使用它的答案,而不是"为什么和何时"我们需要使用它.

通常,我发现它在AJAX中使用.在...上httpRequest.onreadystatechange.这类似于Java的多线程吗?响应的听众如何以及在哪里?异步编程是否类似于多线程?

在以下代码中,控制流程如何:

function some_function(arg1, arg2, callback) {
  var my_number = Math.ceil(Math.random() * (arg1 - arg2) + arg2);
  callback(my_number);
  some_different_function_not_callback(arg1);
}
some_function(5, 15, function(num) {
   console.log("callback called! " + num);
});
Run Code Online (Sandbox Code Playgroud)

来自JQuery网站:

关于回调的特殊之处在于,在"父"之后出现的函数可以在回调执行之前执行"(参考:http://docs.jquery.com/Tutorials:How_jQuery_Works)

有人可以用一个例子向我解释这一行吗?

javascript jquery

28
推荐指数
3
解决办法
2万
查看次数

GHC真的不会内联地图,扫描,折叠等吗?

我注意到GHC手册说"对于自递归函数,环路断路器只能是函数本身,因此INLINE编译指示总是被忽略."

这难道不是说像普通的递归功能结构的每一个应用程序map,zip,scan*,fold*,sum,等不能被内联?

您可以随时重写所有这些功能,添加适当的严格标签,或者使用像这里推荐的"流融合"这样的花哨技术.

然而,这并不是所有这些都极大地限制了我们编写同时快速和优雅的代码的能力吗?

optimization haskell inline ghc

27
推荐指数
2
解决办法
2103
查看次数

函数式编程中的有效递归与不同范式中的低效递归

据我所知,递归非常优雅,但在OOP和程序编程方面效率不高(参见精彩的"High Order perl",Mark Jason Dominus).我有一些信息,在函数式编程递归中很快 - 保持其优雅和简洁.有人可以确认并可能放大这个吗?我正在考虑XSLT和Haskell(我的下一个语言学习列表的高位)

谢谢

丹尼尔

xslt optimization haskell functional-programming tail-recursion

17
推荐指数
3
解决办法
2790
查看次数

链接时优化和内联

根据我的经验,有许多代码明确使用内联函数,这需要权衡:

  1. 代码变得不那么简洁,而且可维护性稍差.
  2. 有时,内联可以大大提高运行时性能.
  3. 内联是在一个固定的时间点决定的,可能没有对其用途的非常好的预知,或者没有考虑所有(未来)周围环境.

问题是:链接时优化(例如,在GCC中)是否呈现手动内联,例如,在C99中声明一个"内联"函数并提供一个实现,已经过时了?我们是否真的不需要考虑自己内联大多数函数?那些总是从内联中受益的函数呢,例如deg_to_rad(x)?

澄清:我不是在考虑同一个翻译单元中的函数,而是考虑逻辑上应该存在于不同翻译单元中的函数.

更新:我经常看到反对"内联",并建议过时.但是,就个人而言,我确实经常看到明确的内联函数:作为类体中定义的函数.

c c++ optimization gcc

15
推荐指数
4
解决办法
8083
查看次数

现代编译器优化如何将递归转换为返回常量?

当我用g ++编译下面的简单递归代码时,汇编代码只返回i,好像g ++可以像人类那样做一些代数技巧.

int Identity(int i) {
  if (i == 1)
    return 1;
  else
    return Identity(i-1)+1;
}
Run Code Online (Sandbox Code Playgroud)

我不认为这种优化是关于尾递归的,显然,g ++必须至少做这两件事:

  1. 如果我们传递一个负值,这个代码将落入一个无限循环,那么g ++是否有效消除这个错误呢?
  2. 虽然可以枚举从1到INT_MAX的所有值,然后g ++可以告诉该函数将返回i,显然g ++使用更智能的检测方法,因为编译过程非常快.因此我的问题是,编译器优化如何做到这一点?

如何重现

% g++ -v
gcc version 8.2.1 20181127 (GCC)

% g++ a.cpp -c -O2 && objdump -d a.o
Disassembly of section .text:
0000000000000000 <_Z8Identityi>:
   0:   89 f8                   mov    %edi,%eax
   2:   c3
Run Code Online (Sandbox Code Playgroud)

更新: 感谢很多人回答这个问题.我在这里收集了一些讨论和更新.

  1. 编译器使用某种方法来知道传递负值会导致UB.也许编译器使用相同的方法来进行代数技巧.
  2. 关于尾递归:根据维基百科,我以前的代码不是尾递归形式.我尝试了尾递归版本,gcc生成了正确的while循环.但是,它不能像我以前的代码那样返回i.
  3. 有人指出编译器可能试图"证明"f(x)= x,但我仍然不知道所用实际优化技术的名称.我对这种优化的确切名称感兴趣,例如常见的子表达式消除(CSE)或它们的某种组合或其他.

更新+回答: 感谢下面的答案(我已经标记它有用,并且还检查了manlio的答案),我想我理解编译器如何以简单的方式完成此操作.请参阅下面的示例.首先,现代gcc可以做一些比尾递归更强大的东西,所以代码转换成这样的东西:

// Equivalent to return i
int Identity_v2(int i) {
  int ans = 0;
  for (int …
Run Code Online (Sandbox Code Playgroud)

c++ recursion gcc g++ compiler-optimization

15
推荐指数
1
解决办法
765
查看次数

如何在函数中修改数组?

MATLAB是一种按值传递的语言.我有一个处理像素邻居的递归函数.每次调用函数时,复制图像(在我的情况下是两个图像)是非常昂贵的.

我使用全局变量来解决问题.有没有其他方法可以使递归函数修改数组?

matlab pass-by-reference pass-by-value

14
推荐指数
2
解决办法
7913
查看次数

我应该避免Prolog中的尾递归吗?

我正在通过"现在学习Prolog"在线书籍来获取乐趣.

我正在尝试编写一个谓词,该谓词遍历列表的每个成员并使用累加器向其中添加一个谓词.我已经很容易做到了,没有尾递归.

addone([],[]).
addone([X|Xs],[Y|Ys]) :- Y is X+1, addone(Xs,Ys).
Run Code Online (Sandbox Code Playgroud)

但我已经读过,出于性能原因,最好避免这种类型的递归.这是真的?总是使用尾递归被认为是"好习惯"吗?是否值得努力使用累加器来养成良好的习惯?

我试图将此示例更改为使用累加器,但它会反转列表.我怎么能避免这个?

accAddOne([X|Xs],Acc,Result) :- Xnew is X+1, accAddOne(Xs,[Xnew|Acc],Result).
accAddOne([],A,A).
addone(List,Result) :- accAddOne(List,[],Result).
Run Code Online (Sandbox Code Playgroud)

tail-recursion prolog accumulator tailrecursion-modulo-cons

13
推荐指数
2
解决办法
5151
查看次数

C中的无限递归

给定具有无限递归的C程序:

int main() {

    main();

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

为什么会导致堆栈溢出.我知道这导致C++中来自以下线程的未定义行为这是无限递归UB吗?(并且作为无法main()在C++中调用的副节点).但是,valgrind告诉我这会导致堆栈溢出:

Stack overflow in thread 1: can't grow stack to 0x7fe801ff8
Run Code Online (Sandbox Code Playgroud)

最后由于分段错误,程序结束:

==2907== Process terminating with default action of signal 11 (SIGSEGV)
==2907==  Access not within mapped region at address 0x7FE801FF0
Run Code Online (Sandbox Code Playgroud)

这是C中的未定义行为,还是应该导致堆栈溢出,为什么会导致堆栈溢出?

编辑

1我想知道C中是否允许无限递归?
2这是否会导致堆栈溢出?(已经得到了充分的回答)

c recursion

13
推荐指数
3
解决办法
1万
查看次数

@tailrec是如何工作的

我使用和阅读有关@tailrec注释的尾部递归方法.我已经通过许多链接解释了它.例如,它仅适用于自调用函数,不应覆盖等.

它到处都提到了compiler optimizes.但是编译器做了什么魔术/概念使其尾递归.对于下面的简单函数,编译器会做什么:

@tailrec def fact(acc: Int, n: Int): Int = {
  if (n <= 1) acc
  else fact(n * acc, n - 1)
}
fact(1,10)
Run Code Online (Sandbox Code Playgroud)

我的意思是它将它转换为循环,重复调用它然后返回最终值?是否有纸张的链接解释它

scala

12
推荐指数
2
解决办法
3613
查看次数