{"id":416673,"date":"2024-06-30T01:06:09","date_gmt":"2024-06-30T01:06:09","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=416673"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2024-06-30T01:06:09","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=416673","title":{"rendered":"<span>On the difference between regular functions and Lambdas<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>The point of this article is to explore Lambda functions, their dirrerences from regular functions and how they are implemented, based on C++, Python and Java programming languages.<\/p>\n<p>Throughout this article I will be using\u00a0<a href=\"http:\/\/godbolt.org\/\" rel=\"noopener noreferrer nofollow\"><u>godbolt.org<\/u><\/a>\u00a0to compile code and see machine code or byte code.<\/p>\n<h2>C++<\/h2>\n<p>What is a function? One could say that a function is a group of statements, prefixed with a function declaration that defines the input and output types. But in terms of machine code, we could say that a function is a sequence of assembly instructions and operands, prefixed with a label with function name and complying with the calling convention (that is, \u201creturns\u201d result and accepts parameters in a certain way and preserves cerain registers).<\/p>\n<p>Besides regular functions, C++ has lambda functions that can be defined inside regular functions. Similarly to ordinary functions, they accept arguments and return values, but, in contrast to regular functions, they can \u201ccapture\u201d, either by value or by reference, variables from the scope where they are defined. Capturing means that the lambda function can access (read or write) variables in the outer scope or their copies. Let`s see some examples and which assembly code they are compiled to. I will use gcc 12.2 with\u00a0<code>-Os<\/code>\u00a0flag (the less assembly will be generated, the easier to read).<\/p>\n<pre><code class=\"cpp\">#include &lt;vector> #include &lt;cstdio>  \/\/ extern so that it won`t be inlined extern void for_each(const std::vector&lt;int>&amp; vec, void (*func) (int));  void example(const std::vector&lt;int>&amp; vec) {     for_each(vec, [] (int n) {         printf(\"%d\\n\", n);     }); }<\/code><\/pre>\n<pre><code class=\"assembly\">.LC0:         .string \"%d\\n\" example(std::vector&lt;int, std::allocator&lt;int> > const&amp;)::{lambda(int)#1}::_FUN(int):         movl    %edi, %esi         xorl    %eax, %eax         movl    $.LC0, %edi         jmp     printf example(std::vector&lt;int, std::allocator&lt;int> > const&amp;):         movl    $example(std::vector&lt;int, std::allocator&lt;int> > const&amp;)::{lambda(int)#1}::_FUN(int), %esi         jmp     for_each(std::vector&lt;int, std::allocator&lt;int> > const&amp;, void (*)(int)) \/\/ note that using jmp instead of call is an optimization; the called  \/\/ function will return to the callee of the current function, skipping `example`<\/code><\/pre>\n<p>So far, we haven`t seen anything interesting: the lambda function that we declared inside\u00a0<code>example<\/code>\u00a0was simply declared as a normal function in the assembly.<\/p>\n<p>Let`s see a more interesting example, where our lambda would capture a variable from the outside.<\/p>\n<pre><code class=\"cpp\">#include &lt;vector> #include &lt;cstdio>  extern void for_each(const std::vector&lt;int>&amp; vec, void (*func) (int));  void example(const std::vector&lt;int>&amp; vec, int threshold) {     for_each(vec, [threshold] (int n) {         if (n > threshold) {             printf(\"%d\\n\", n);         }     }); }<\/code><\/pre>\n<p>However, this won`t compile:<\/p>\n<pre><code class=\"cpp\">&lt;source>: In function 'void example(const std::vector&lt;int>&amp;, int)': &lt;source>:7:19: error: cannot convert 'example(const std::vector&lt;int>&amp;, int)::&lt;lambda(int)>' to 'void (*)(int)'     7 |     for_each(vec, [threshold] (int n) {       |                   ^~~~~~~~~~~~~~~~~~~~~       |                   |       |                   example(const std::vector&lt;int>&amp;, int)::&lt;lambda(int)>     8 |         if (n > threshold) {       |         ~~~~~~~~~~~~~~~~~~~~     9 |             printf(\"%d\\n\", n);       |             ~~~~~~~~~~~~~~~~~~    10 |         }       |         ~              11 |     });       |     ~               &lt;source>:4:58: note:   initializing argument 2 of 'void for_each(const std::vector&lt;int>&amp;, void (*)(int))'     4 | extern void for_each(const std::vector&lt;int>&amp; vec, void (*func) (int));       |                                                   ~~~~~~~^~~~~~~~~~~ Compiler returned: 1<\/code><\/pre>\n<p>It turns out that a lambda function declared inside another function will have a more complex type than just a normal function, in this case,\u00a0<code>example(const std::vector&lt;int>&amp;, int)::&lt;lambda(int)><\/code>\u00a0\u2014 the type will mention the outer function.<\/p>\n<p>This means\u00a0<strong>two important<\/strong>\u00a0things. Firstly, the lambda function we wrote can`t be compiled to an assembly function and is not a function in the sense of the definition given above. Secondly, the lambda function can either be used inside the scope where it was defined or passed to a\u00a0<strong>template<\/strong>\u00a0function (a template function is a template, which is used by the compiler to generate function definitions based on the the substitutions of template arguments), which means that a new implementation of the template function will be generated by the compiler for\u00a0<strong>each<\/strong>\u00a0lambda function we declare. Let`s see an example:<\/p>\n<pre><code class=\"cpp\">#include &lt;vector> #include &lt;cstdio>  template &lt;typename Callable> void __attribute__ ((noinline)) for_each(const std::vector&lt;int>&amp; vec, Callable func) {     for (const auto&amp; elem : vec) {         func(elem);     } }  void example(const std::vector&lt;int>&amp; vec, int threshold) {     for_each(vec, [threshold] (int n) {         if (n > threshold) {             printf(\"%d\\n\", n);         }     }); }<\/code><\/pre>\n<pre><code class=\"assembly\">.LC0:         .string \"%d\\n\" void for_each&lt;example(std::vector&lt;int, std::allocator&lt;int> > const&amp;, int)::{lambda(int)#1}>(std::vector&lt;int, std::allocator&lt;int> > const&amp;, example(std::vector&lt;int, std::allocator&lt;int> > const&amp;, int)::{lambda(int)#1}):         pushq   %r12         pushq   %rbp         movl    %esi, %ebp         pushq   %rbx         movq    8(%rdi), %r12         movq    (%rdi), %rbx .L2:         cmpq    %rbx, %r12         je      .L7         movl    (%rbx), %esi         cmpl    %ebp, %esi         jle     .L3         movl    $.LC0, %edi         xorl    %eax, %eax         call    printf .L3:         addq    $4, %rbx         jmp     .L2 .L7:         popq    %rbx         popq    %rbp         popq    %r12         ret example(std::vector&lt;int, std::allocator&lt;int> > const&amp;, int):         jmp     void for_each&lt;example(std::vector&lt;int, std::allocator&lt;int> > const&amp;, int)::{lambda(int)#1}>(std::vector&lt;int, std::allocator&lt;int> > const&amp;, example(std::vector&lt;int, std::allocator&lt;int> > const&amp;, int)::{lambda(int)#1})<\/code><\/pre>\n<p>As we can see, an implementation of\u00a0<code>for_each<\/code>\u00a0was generated and the lambda function was inlined into it \u2014 and\u00a0<code>for_each<\/code>\u00a0turned actually to be a function accepting a\u00a0<code>vector<\/code>\u00a0and an\u00a0<code>integer<\/code>.<\/p>\n<p>This means that the implementation of lambda functions in C++ is based on templates \u2014 and in most cases, it will be inlined.<\/p>\n<p>Let`s look at a slightly more complex example:<\/p>\n<pre><code>#include &lt;vector> #include &lt;cstdio>  template &lt;typename Callable> void __attribute__ ((noinline)) for_each(const std::vector&lt;int>&amp; vec, Callable func) {     for (const auto&amp; elem : vec) {         func(elem);     } }  auto get_filtering_lambda(int threshold) {     return [threshold] (int n) {         if (n > threshold) {             printf(\"%d\\n\", n);         }     }; }  void example(const std::vector&lt;int>&amp; vec, int threshold) {     for_each(vec, get_filtering_lambda(threshold)); }<\/code><\/pre>\n<p>What assembly would we expect to see here? As we remember,\u00a0<code>for_each<\/code>\u00a0does not actually accept a function, rather, it accepts the values and references that the lambda captures. This means that\u00a0<code>get_filtering_lambda<\/code>\u00a0only needs to provide these captured values:<\/p>\n<pre><code class=\"assembly\">.LC0:         .string \"%d\\n\" void for_each&lt;get_filtering_lambda(int)::{lambda(int)#1}>(std::vector&lt;int, std::allocator&lt;int> > const&amp;, get_filtering_lambda(int)::{lambda(int)#1}):         pushq   %r12         pushq   %rbp         movl    %esi, %ebp         pushq   %rbx         movq    8(%rdi), %r12         movq    (%rdi), %rbx .L2:         cmpq    %rbx, %r12         je      .L7         movl    (%rbx), %esi         cmpl    %ebp, %esi         jle     .L3         movl    $.LC0, %edi         xorl    %eax, %eax         call    printf .L3:         addq    $4, %rbx         jmp     .L2 .L7:         popq    %rbx         popq    %rbp         popq    %r12         ret get_filtering_lambda(int):         movl    %edi, %eax         ret example(std::vector&lt;int, std::allocator&lt;int> > const&amp;, int):         jmp     void for_each&lt;get_filtering_lambda(int)::{lambda(int)#1}>(std::vector&lt;int, std::allocator&lt;int> > const&amp;, get_filtering_lambda(int)::{lambda(int)#1})<\/code><\/pre>\n<p>Note that the template implementation of lambdas is not the only possible one. The other possible one would rely on virtual classes. We could translate a lambda function into a regular function, that accepts the arguments that the lambda accepts and the values that the lambda captures. Then the function can be packed with the captured values into an obect and passed to a function.<\/p>\n<pre><code class=\"cpp\">class IIntConsumer { public:     virtual ~IIntConsumer() = 0;      virtual void consume(int) = 0; };  void for_reach(const vector&lt;int>&amp; vec, IIntConsumer* func) {     for (const auto&amp; elem : vec) {         func->consume(elem);     } }<\/code><\/pre>\n<p>For our lambda function from the second example above, the compiler would generate a class, extending\u00a0<code>IIntConsumer<\/code>\u00a0and a function of two arguments, then create an object of that class and pass it to\u00a0<code>for_each<\/code>.<\/p>\n<pre><code class=\"cpp\">void lambda_translation(int a, int captured0) {     if (a > captured0) {         printf(\"%d\", a);     } }  class LambdaHolder: public IIntConsumer { public:     LambdaHolder(int a, int b, int captured0) : captured0(captured0) {}          void consume(int a) override {         lambda_translation(a, captured0);     }      private:     int captured0; };<\/code><\/pre>\n<p>The advantage of such an approach is that the implementation of\u00a0<code>for_each<\/code>\u00a0and other functuions accepting lambdas would require just one implementation, which would reduce the size of the resulting binaries (and therefore, potentially reduice iTLB misses) and compilation time. However, calling virtual methods is more expensive than normal functions \u2014 and they can`t be inlined \u2014 therefore, in most cases, performance would be worse.<\/p>\n<h2>Python<\/h2>\n<p>Let`s start by writing some code and see how it is compiled into Python bytecode. I will use CPython 3.7. Python lambdas are one-line functions, so they aren`t very expressive \u2014 however, a similar concept to C++ lambdas are functions declared inside other functions \u2014 they can \u201ccapture\u201d variables from the outside.<\/p>\n<p>Before examining Python bytecode, let`s note three things. Firstly, the numbers in the left column correspond to the lines of the original source code (lines are enumerated starting from 1). This way, we can easily see which bytecode each line results in. Secondly, python bytecode is organised differently than machine code resulting from C, C++ or any other compiled language. In the latter case, the binary header will have the address of the instruction that should be the first one executed when the binary is executed. This instruction would be the beginning of the\u00a0<code>_start<\/code>\u00a0function of the original program (most source programs don`t define it; this function is defined in C standard library and it calls the familiar\u00a0<code>main<\/code>\u00a0after some preparation). On the other hand, in Python, the byte code will be executed from the first instruction and execute until\u00a0<code>return<\/code>\u00a0is reached. Finally, note that Python bytecode is executed on a stack machine, while an i386\/amd64 processor is a register-based machine.<\/p>\n<pre><code>def func_generator(a):     def inner(b):         return a &lt; b      return inner  print(func_generator(2)(3))<\/code><\/pre>\n<pre><code class=\"assembly\">1           0 LOAD_CONST               0 (&lt;code object func_generator at 0x55ccef231910, file \"example.py\", line 1>)               2 LOAD_CONST               1 ('func_generator')               4 MAKE_FUNCTION            0               6 STORE_NAME               0 (func_generator)    7           8 LOAD_NAME                1 (print)              10 LOAD_NAME                0 (func_generator)              12 LOAD_CONST               2 (2)              14 CALL_FUNCTION            1              16 LOAD_CONST               3 (3)              18 CALL_FUNCTION            1              20 CALL_FUNCTION            1              22 POP_TOP              24 LOAD_CONST               4 (None)              26 RETURN_VALUE  Disassembly of &lt;code object func_generator at 0x55ccef231910, file \"example.py\", line 1>:   2           0 LOAD_CLOSURE             0 (a)               2 BUILD_TUPLE              1               4 LOAD_CONST               1 (&lt;code object inner at 0x55ccef204fc0, file \"example.py\", line 2>)               6 LOAD_CONST               2 ('func_generator.&lt;locals>.inner')               8 MAKE_FUNCTION            8              10 STORE_FAST               1 (inner)    5          12 LOAD_FAST                1 (inner)              14 RETURN_VALUE  Disassembly of &lt;code object inner at 0x55ccef204fc0, file \"example.py\", line 2>:   3           0 LOAD_DEREF               0 (a)               2 LOAD_FAST                0 (b)               4 COMPARE_OP               0 (&lt;)               6 RETURN_VALUE<\/code><\/pre>\n<p>So, what happens in the bytecode above? First, the first line of the original Python source is executed, which pushes onto the stack the reference to the bytecode of the\u00a0<code>func_generator<\/code>\u00a0function and the name of the function. Then the\u00a0<code>MAKE_FUNCTION<\/code>instruction is executed, which will create a function object within the Python VM. FInally, the newly created function object is saved into the\u00a0<code>func_generator<\/code>\u00a0variable.<\/p>\n<p>The next line to be executed is the 7th. It pushes onto the stack the\u00a0<code>print<\/code>\u00a0and\u00a0<code>func_generator<\/code>\u00a0functions, the argiment to the latter, calls it, pushes the argument to the generated function, then calls the generated function and the\u00a0<code>print<\/code>\u00a0function and returns\u00a0<code>None<\/code>.<\/p>\n<p>When the\u00a0<code>func_generator<\/code>is called, the second line of code is executed. It loads the\u00a0<code>a<\/code>\u00a0variable (note that\u00a0<code>LOAD_CLOSURE<\/code>\u00a0and\u00a0<code>LOAD_FAST<\/code>\u00a0is the same thing), creates a tuple containig it, then loads a reference to the code of\u00a0<code>inner<\/code>\u00a0function and a name for the function. Then\u00a0<code>MAKE_FUNCTION<\/code>\u00a0is called \u2014 it appears that it is passed, in this case, a tuple of variables that the inner function will capture. This way, a Python function is not only a sequence of bytecode instructions, but also some auxiliary information, like variables captured from the outside. For confirmation, let`s read the description of the insruction in Python\u00a0<a href=\"https:\/\/docs.python.org\/3\/library\/dis.html\" rel=\"noopener noreferrer nofollow\"><u>docs<\/u><\/a>:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/5c3\/bd4\/d38\/5c3bd4d38ca82e4c52b323552672e41d.png\" alt=\"\" title=\"\" width=\"700\" height=\"261\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/5c3\/bd4\/d38\/5c3bd4d38ca82e4c52b323552672e41d.png\"\/><\/figure>\n<p>Finally, let`s see what the\u00a0<code>inner<\/code>\u00a0function does (line 3). It loads the parameter onto the stack, then the variable captured from the outside (it would be read from the function object) and compares them.<\/p>\n<p>To conclude, we have seen that:<\/p>\n<ol>\n<li>\n<p>Python functions are not just sequences of bytecode instructions, but objects containing some data<\/p>\n<\/li>\n<li>\n<p>Support for capturing values from the outside is provided by the Python VM<\/p>\n<\/li>\n<li>\n<p>A Python program may, thus, generate an arbitrary number of functions<\/p>\n<\/li>\n<\/ol>\n<h2>Java<\/h2>\n<p>Now let`s look what happens in Java. I will be using openjdk 19.<\/p>\n<pre><code class=\"java\">import java.util.ArrayList; import java.util.concurrent.atomic.*;  class Example {     public static void caller3(ArrayList&lt;Integer> a, AtomicInteger b, AtomicBoolean boo) {         a.forEach((i) -> {             if (i > b.get()) {                 System.out.println(i);                 boo.set(true);             } else {                 b.decrementAndGet();                 boo.set(false);             }         });     } }<\/code><\/pre>\n<p>The resulting bytecode will be:<\/p>\n<pre><code class=\"java\">class Example {    public static void caller3(java.util.ArrayList&lt;java.lang.Integer>, java.util.concurrent.atomic.AtomicInteger, java.util.concurrent.atomic.AtomicBoolean);        0: aload_0        1: aload_1        2: aload_2        3: invokedynamic #7,  0              \/\/ InvokeDynamic #0:accept:(Ljava\/util\/concurrent\/atomic\/AtomicInteger;Ljava\/util\/concurrent\/atomic\/AtomicBoolean;)Ljava\/util\/function\/Consumer;        8: invokevirtual #11                 \/\/ Method java\/util\/ArrayList.forEach:(Ljava\/util\/function\/Consumer;)V       11: return    private static void lambda$caller3$0(java.util.concurrent.atomic.AtomicInteger, java.util.concurrent.atomic.AtomicBoolean, java.lang.Integer);        0: aload_2        1: invokevirtual #17                 \/\/ Method java\/lang\/Integer.intValue:()I        4: aload_0        5: invokevirtual #23                 \/\/ Method java\/util\/concurrent\/atomic\/AtomicInteger.get:()I        8: if_icmple     26       11: getstatic     #28                 \/\/ Field java\/lang\/System.out:Ljava\/io\/PrintStream;       14: aload_2       15: invokevirtual #34                 \/\/ Method java\/io\/PrintStream.println:(Ljava\/lang\/Object;)V       18: aload_1       19: iconst_1       20: invokevirtual #40                 \/\/ Method java\/util\/concurrent\/atomic\/AtomicBoolean.set:(Z)V       23: goto          36       26: aload_0       27: invokevirtual #46                 \/\/ Method java\/util\/concurrent\/atomic\/AtomicInteger.decrementAndGet:()I       30: pop       31: aload_1       32: iconst_0       33: invokevirtual #40                 \/\/ Method java\/util\/concurrent\/atomic\/AtomicBoolean.set:(Z)V       36: return  }<\/code><\/pre>\n<p>We can see that, firstly, the lambda function resulted in a static function that accepts both the variables that the lambda captures from the outside and secondly, that before calling\u00a0<code>ArrayList::forEach<\/code>, it calls something and as a result it gets an object of a class that implements\u00a0<code>Consumer&lt;Integer><\/code>. It is not that important now, how exactly this call is working \u2014 you can read about this in more detail\u00a0<a href=\"https:\/\/jp-wang.github.io\/deep-into-lambda2\/\" rel=\"noopener noreferrer nofollow\"><u>here<\/u><\/a>. What is more intgeresting for us, is what exactly class will be implementing\u00a0<code>Consumer&lt;Integer><\/code>. It turns out that the class is generated by the JVM at runtime and can be seen if we add the &#8212;<code>jdk.internal.lambda.dumpProxyClasses<\/code>\u00a0option to the JVM. After decompiling the class generated at runtime will be:<\/p>\n<pre><code class=\"java\">import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer;  \/\/ $FF: synthetic class final class Lambder$$Lambda$1 implements Consumer {     private final AtomicInteger arg$1;      private Lambder$$Lambda$1(AtomicInteger var1) {         this.arg$1 = var1;     }      public void accept(Object var1) {         Lambder.lambda$caller2$0(this.arg$1, (Integer)var1);     } }<\/code><\/pre>\n<p>So, what happens in Java is the following:<\/p>\n<ol>\n<li>\n<p>For a lambda function, the java compiler generates another function, which accepts both the objects captured from the outside and the arguments of the lambda function.<\/p>\n<\/li>\n<li>\n<p>At runtime, the JVM generates a class that can hold the objects that the lambda captures and call the lambda with those arguments and arguments that the lambda accepts, creates an object of this class with the captured obects.<\/p>\n<\/li>\n<li>\n<p>The object of the autogenerated class is passed to functions that accept functional interfaces.<\/p>\n<\/li>\n<\/ol>\n<p>Note that the solution implemented for Java is almost same as the theoretical approach for C++ that doesn`t require such extensive use of templates. When discussing C++, we said that this approach is worse in terms of performance. However, that conclusion was made based on the fact that code is executed on an x86\/amd64 processor and not a JVM \u2014which means it doesn`t directly apply here. Besides that, Java doesn`t have templates.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/731864\/\"> https:\/\/habr.com\/ru\/articles\/731864\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>The point of this article is to explore Lambda functions, their dirrerences from regular functions and how they are implemented, based on C++, Python and Java programming languages.<\/p>\n<p>Throughout this article I will be using\u00a0<a href=\"http:\/\/godbolt.org\/\" rel=\"noopener noreferrer nofollow\"><u>godbolt.org<\/u><\/a>\u00a0to compile code and see machine code or byte code.<\/p>\n<h2>C++<\/h2>\n<p>What is a function? One could say that a function is a group of statements, prefixed with a function declaration that defines the input and output types. But in terms of machine code, we could say that a function is a sequence of assembly instructions and operands, prefixed with a label with function name and complying with the calling convention (that is, \u201creturns\u201d result and accepts parameters in a certain way and preserves cerain registers).<\/p>\n<p>Besides regular functions, C++ has lambda functions that can be defined inside regular functions. Similarly to ordinary functions, they accept arguments and return values, but, in contrast to regular functions, they can \u201ccapture\u201d, either by value or by reference, variables from the scope where they are defined. Capturing means that the lambda function can access (read or write) variables in the outer scope or their copies. Let`s see some examples and which assembly code they are compiled to. I will use gcc 12.2 with\u00a0<code>-Os<\/code>\u00a0flag (the less assembly will be generated, the easier to read).<\/p>\n<pre><code class=\"cpp\">#include &lt;vector> #include &lt;cstdio>  \/\/ extern so that it won`t be inlined extern void for_each(const std::vector&lt;int>&amp; vec, void (*func) (int));  void example(const std::vector&lt;int>&amp; vec) {     for_each(vec, [] (int n) {         printf(\"%d\\n\", n);     }); }<\/code><\/pre>\n<pre><code class=\"assembly\">.LC0:         .string \"%d\\n\" example(std::vector&lt;int, std::allocator&lt;int> > const&amp;)::{lambda(int)#1}::_FUN(int):         movl    %edi, %esi         xorl    %eax, %eax         movl    $.LC0, %edi         jmp     printf example(std::vector&lt;int, std::allocator&lt;int> > const&amp;):         movl    $example(std::vector&lt;int, std::allocator&lt;int> > const&amp;)::{lambda(int)#1}::_FUN(int), %esi         jmp     for_each(std::vector&lt;int, std::allocator&lt;int> > const&amp;, void (*)(int)) \/\/ note that using jmp instead of call is an optimization; the called  \/\/ function will return to the callee of the current function, skipping `example`<\/code><\/pre>\n<p>So far, we haven`t seen anything interesting: the lambda function that we declared inside\u00a0<code>example<\/code>\u00a0was simply declared as a normal function in the assembly.<\/p>\n<p>Let`s see a more interesting example, where our lambda would capture a variable from the outside.<\/p>\n<pre><code class=\"cpp\">#include &lt;vector> #include &lt;cstdio>  extern void for_each(const std::vector&lt;int>&amp; vec, void (*func) (int));  void example(const std::vector&lt;int>&amp; vec, int threshold) {     for_each(vec, [threshold] (int n) {         if (n > threshold) {             printf(\"%d\\n\", n);         }     }); }<\/code><\/pre>\n<p>However, this won`t compile:<\/p>\n<pre><code class=\"cpp\">&lt;source>: In function 'void example(const std::vector&lt;int>&amp;, int)': &lt;source>:7:19: error: cannot convert 'example(const std::vector&lt;int>&amp;, int)::&lt;lambda(int)>' to 'void (*)(int)'     7 |     for_each(vec, [threshold] (int n) {       |                   ^~~~~~~~~~~~~~~~~~~~~       |                   |       |                   example(const std::vector&lt;int>&amp;, int)::&lt;lambda(int)>     8 |         if (n > threshold) {       |         ~~~~~~~~~~~~~~~~~~~~     9 |             printf(\"%d\\n\", n);       |             ~~~~~~~~~~~~~~~~~~    10 |         }       |         ~              11 |     });       |     ~               &lt;source>:4:58: note:   initializing argument 2 of 'void for_each(const std::vector&lt;int>&amp;, void (*)(int))'     4 | extern void for_each(const std::vector&lt;int>&amp; vec, void (*func) (int));       |                                                   ~~~~~~~^~~~~~~~~~~ Compiler returned: 1<\/code><\/pre>\n<p>It turns out that a lambda function declared inside another function will have a more complex type than just a normal function, in this case,\u00a0<code>example(const std::vector&lt;int>&amp;, int)::&lt;lambda(int)><\/code>\u00a0\u2014 the type will mention the outer function.<\/p>\n<p>This means\u00a0<strong>two important<\/strong>\u00a0things. Firstly, the lambda function we wrote can`t be compiled to an assembly function and is not a function in the sense of the definition given above. Secondly, the lambda function can either be used inside the scope where it was defined or passed to a\u00a0<strong>template<\/strong>\u00a0function (a template function is a template, which is used by the compiler to generate function definitions based on the the substitutions of template arguments), which means that a new implementation of the template function will be generated by the compiler for\u00a0<strong>each<\/strong>\u00a0lambda function we declare. Let`s see an example:<\/p>\n<pre><code class=\"cpp\">#include &lt;vector> #include &lt;cstdio>  template &lt;typename Callable> void __attribute__ ((noinline)) for_each(const std::vector&lt;int>&amp; vec, Callable func) {     for (const auto&amp; elem : vec) {         func(elem);     } }  void example(const std::vector&lt;int>&amp; vec, int threshold) {     for_each(vec, [threshold] (int n) {         if (n > threshold) {             printf(\"%d\\n\", n);         }     }); }<\/code><\/pre>\n<pre><code class=\"assembly\">.LC0:         .string \"%d\\n\" void for_each&lt;example(std::vector&lt;int, std::allocator&lt;int> > const&amp;, int)::{lambda(int)#1}>(std::vector&lt;int, std::allocator&lt;int> > const&amp;, example(std::vector&lt;int, std::allocator&lt;int> > const&amp;, int)::{lambda(int)#1}):         pushq   %r12         pushq   %rbp         movl    %esi, %ebp         pushq   %rbx         movq    8(%rdi), %r12         movq    (%rdi), %rbx .L2:         cmpq    %rbx, %r12         je      .L7         movl    (%rbx), %esi         cmpl    %ebp, %esi         jle     .L3         movl    $.LC0, %edi         xorl    %eax, %eax         call    printf .L3:         addq    $4, %rbx         jmp     .L2 .L7:         popq    %rbx         popq    %rbp         popq    %r12         ret example(std::vector&lt;int, std::allocator&lt;int> > const&amp;, int):         jmp     void for_each&lt;example(std::vector&lt;int, std::allocator&lt;int> > const&amp;, int)::{lambda(int)#1}>(std::vector&lt;int, std::allocator&lt;int> > const&amp;, example(std::vector&lt;int, std::allocator&lt;int> > const&amp;, int)::{lambda(int)#1})<\/code><\/pre>\n<p>As we can see, an implementation of\u00a0<code>for_each<\/code>\u00a0was generated and the lambda function was inlined into it \u2014 and\u00a0<code>for_each<\/code>\u00a0turned actually to be a function accepting a\u00a0<code>vector<\/code>\u00a0and an\u00a0<code>integer<\/code>.<\/p>\n<p>This means that the implementation of lambda functions in C++ is based on templates \u2014 and in most cases, it will be inlined.<\/p>\n<p>Let`s look at a slightly more complex example:<\/p>\n<pre><code>#include &lt;vector> #include &lt;cstdio>  template &lt;typename Callable> void __attribute__ ((noinline)) for_each(const std::vector&lt;int>&amp; vec, Callable func) {     for (const auto&amp; elem : vec) {         func(elem);     } }  auto get_filtering_lambda(int threshold) {     return [threshold] (int n) {         if (n > threshold) {             printf(\"%d\\n\", n);         }     }; }  void example(const std::vector&lt;int>&amp; vec, int threshold) {     for_each(vec, get_filtering_lambda(threshold)); }<\/code><\/pre>\n<p>What assembly would we expect to see here? As we remember,\u00a0<code>for_each<\/code>\u00a0does not actually accept a function, rather, it accepts the values and references that the lambda captures. This means that\u00a0<code>get_filtering_lambda<\/code>\u00a0only needs to provide these captured values:<\/p>\n<pre><code class=\"assembly\">.LC0:         .string \"%d\\n\" void for_each&lt;get_filtering_lambda(int)::{lambda(int)#1}>(std::vector&lt;int, std::allocator&lt;int> > const&amp;, get_filtering_lambda(int)::{lambda(int)#1}):         pushq   %r12         pushq   %rbp         movl    %esi, %ebp         pushq   %rbx         movq    8(%rdi), %r12         movq    (%rdi), %rbx .L2:         cmpq    %rbx, %r12         je      .L7         movl    (%rbx), %esi         cmpl    %ebp, %esi         jle     .L3         movl    $.LC0, %edi         xorl    %eax, %eax         call    printf .L3:         addq    $4, %rbx         jmp     .L2 .L7:         popq    %rbx         popq    %rbp         popq    %r12         ret get_filtering_lambda(int):         movl    %edi, %eax         ret example(std::vector&lt;int, std::allocator&lt;int> > const&amp;, int):         jmp     void for_each&lt;get_filtering_lambda(int)::{lambda(int)#1}>(std::vector&lt;int, std::allocator&lt;int> > const&amp;, get_filtering_lambda(int)::{lambda(int)#1})<\/code><\/pre>\n<p>Note that the template implementation of lambdas is not the only possible one. The other possible one would rely on virtual classes. We could translate a lambda function into a regular function, that accepts the arguments that the lambda accepts and the values that the lambda captures. Then the function can be packed with the captured values into an obect and passed to a function.<\/p>\n<pre><code class=\"cpp\">class IIntConsumer { public:     virtual ~IIntConsumer() = 0;      virtual void consume(int) = 0; };  void for_reach(const vector&lt;int>&amp; vec, IIntConsumer* func) {     for (const auto&amp; elem : vec) {         func->consume(elem);     } }<\/code><\/pre>\n<p>For our lambda function from the second example above, the compiler would generate a class, extending\u00a0<code>IIntConsumer<\/code>\u00a0and a function of two arguments, then create an object of that class and pass it to\u00a0<code>for_each<\/code>.<\/p>\n<pre><code class=\"cpp\">void lambda_translation(int a, int captured0) {     if (a > captured0) {         printf(\"%d\", a);     } }  class LambdaHolder: public IIntConsumer { public:     LambdaHolder(int a, int b, int captured0) : captured0(captured0) {}          void consume(int a) override {         lambda_translation(a, captured0);     }      private:     int captured0; };<\/code><\/pre>\n<p>The advantage of such an approach is that the implementation of\u00a0<code>for_each<\/code>\u00a0and other functuions accepting lambdas would require just one implementation, which would reduce the size of the resulting binaries (and therefore, potentially reduice iTLB misses) and compilation time. However, calling virtual methods is more expensive than normal functions \u2014 and they can`t be inlined \u2014 therefore, in most cases, performance would be worse.<\/p>\n<h2>Python<\/h2>\n<p>Let`s start by writing some code and see how it is compiled into Python bytecode. I will use CPython 3.7. Python lambdas are one-line functions, so they aren`t very expressive \u2014 however, a similar concept to C++ lambdas are functions declared inside other functions \u2014 they can \u201ccapture\u201d variables from the outside.<\/p>\n<p>Before examining Python bytecode, let`s note three things.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-416673","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/416673","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=416673"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/416673\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=416673"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=416673"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=416673"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}