DEV Community

loading...

A simple comparison of Rust and C using Radare2

aibhstin profile image Aibhstin ・10 min read

The rust code:

fn main() {
    for i in 0..5 {
        println!("hello friend [{}]", i);
    }
}
Enter fullscreen mode Exit fullscreen mode

And the C code:

#include <stdio.h>

int main(void) {
    int i;
    for (i = 0; i < 5; i++) {
        printf("hello friend [%d]\n", i);
    }
}
Enter fullscreen mode Exit fullscreen mode

I've left out return 0 from the C code in this case.

A quick observation based only on the source code is that the Rust code is more succinct. Achieving the same result in slightly fewer lines of code. I find the purpose of the rust code also shines through a bit more than in the C code, but this is a difficult comparison to make given how simple the programs are.

As another comparison, I timed the compilation time for each program. For Rust:

$ time rustc rust.rs

________________________________________________________
Executed in  419.58 millis    fish           external 
   usr time  322.64 millis  592.00 micros  322.05 millis 
   sys time   75.29 millis  244.00 micros   75.05 millis 

Enter fullscreen mode Exit fullscreen mode

and for C:

$ time gcc -o c c.c

________________________________________________________
Executed in   67.53 millis    fish           external 
   usr time   52.81 millis  462.00 micros   52.34 millis 
   sys time   14.11 millis  159.00 micros   13.95 millis 

Enter fullscreen mode Exit fullscreen mode

As was probably expected, C is much faster to compile, at least with this simple program. It would be interesting to see if C retained this speed advantage with larger and more complex programs.

Next, we'll try timing the execution speed of each program. First, Rust:

$ time ./rust 
hello friend [0]
hello friend [1]
hello friend [2]
hello friend [3]
hello friend [4]

________________________________________________________
Executed in    1.70 millis    fish           external 
   usr time  1560.00 micros  340.00 micros  1220.00 micros 
   sys time  140.00 micros  140.00 micros    0.00 micros 

Enter fullscreen mode Exit fullscreen mode

and now C:

$ time ./c
hello friend [0]
hello friend [1]
hello friend [2]
hello friend [3]
hello friend [4]

________________________________________________________
Executed in    2.11 millis    fish           external 
   usr time    1.99 millis  470.00 micros  1524.00 micros 
   sys time    0.14 millis  139.00 micros    0.00 micros 

Enter fullscreen mode Exit fullscreen mode

Interestingly, Rust is faster in this case. Again, this comparison was made with a very simple program and may not be indicative of the speed comparison with larger and more complex program, but the result is interesting nonetheless.

For the next part I opened both in Radare2 and performed analysis using aaaa. I don't have the exact times for either but the C program was analyzed almost instantly, whereas the Rust program took a good few seconds. Next I tried listing the strings found in the data section in each binary. C is first this time:

[0x00001040]> iz
[Strings]
nth paddr      vaddr      len size section type  string
―――――――――――――――――――――――――――――――――――――――――――――――――――――――
0   0x00002004 0x00002004 18  19   .rodata ascii hello friend [%d]\n
Enter fullscreen mode Exit fullscreen mode

Radare2 finds a single string in the data section containing our 'hello friend' string. Now for Rust:

[0x00005070]> iz                                                                                                                      
[Strings]                                                                                                                             
nth paddr      vaddr      len  size section type    string                                                                            
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――                                                                            
0   0x00036000 0x00036000 16   17   .rodata ascii   hello friend []\n                                                                 
1   0x00036080 0x00036080 32   33   .rodata ascii   uespemosarenegylmodnarodsetybdet                                                  
2   0x00036100 0x00036100 16   17   .rodata ascii                                                                                     
3   0x000362d0 0x000362d0 192  193  .rodata ascii   already borrowedconnection resetentity not foundStdinLock { .. }prefix not found  
           at NotNulTerminatedAddrNotAvailablePermissionDeniedStripPrefixErrorinvalid argumentavx512vpclmulqdq                        
4   0x00037288 0x00037288 165  166  .rodata ascii   /rustc/e1884a8e3c3e813aada8254edfa120e85bf5ffca/library/alloc/src/collections/btre
e/map.rs/rustc/e1884a8e3c3e813aada8254edfa120e85bf5ffca/library/core/src/num/mod.rs                                                   
5   0x00037330 0x00037330 25   26   .rodata ascii   attempt to divide by zero            
...
...
...
     58  0x0003a6ad 0x0003a6ad 4    5    .rodata ascii   :?EQ
59  0x0003a6b9 0x0003a6b9 4    5    .rodata ascii   "%>?
60  0x0003a6c0 0x0003a6c0 27   28   .rodata ascii    #%&(38:HJLPSUVXZ\^`cefksx}
61  0x0003a710 0x0003a710 4    5    .rodata ascii   (\b4\v
62  0x0003a721 0x0003a721 4    5    .rodata ascii   c\b\t0
63  0x0003a733 0x0003a733 7    8    .rodata ascii   \n\a\t\a@ '
64  0x0003a744 0x0003a744 12   13   .rodata ascii   \f\aPI73\r3\a.\b\n
65  0x0003a751 0x0003a751 7    8    .rodata ascii   &RN(\b*V
66  0x0003a766 0x0003a766 9    10   .rodata ascii   H\b'\tu\v?A*
67  0x0003a784 0x0003a784 5    6    .rodata ascii   ^"E\v\n
68  0x0003a78c 0x0003a78c 5    6    .rodata ascii   9\a\n6,
69  0x0003a795 0x0003a795 13   14   .rodata ascii   <dS\fH\t\nFE\eH\bS
70  0x0003a7d5 0x0003a7d5 6    7    .rodata ascii   `&\nF\n(
71  0x0003a80a 0x0003a80a 4    5    .rodata ascii   \ns\bn
72  0x0003a84d 0x0003a84d 4    5    .rodata ascii   d\fV\n
73  0x0003a866 0x0003a866 5    6    .rodata ascii   t\fY\a\f
74  0x0003a872 0x0003a872 4    5    .rodata ascii   (\b"N
75  0x0003a88d 0x0003a88d 121  122  .rodata ascii   library/core/src/unicode/unicode_data.rsEmptyParseIntErrorNegOverflowPosOverflowInvalidDigitUtf8Errorvalid_up_toerror_len
76  0x0003a921 0x0003a921 5    6    .rodata ascii   , +*0
77  0x0003a943 0x0003a943 5    6    .rodata ascii   6$\ra7
78  0x0003a959 0x0003a959 5    6    .rodata ascii   jaNOo

Enter fullscreen mode Exit fullscreen mode

In the C binary we only found a single string as expected, but the Rust binary contains 79 strings apparently in the data section of the binary.

Next, let's list functions with afl. C:

[0x00001040]> afl
0x00001040    1 46           entry0
0x00001070    4 41   -> 34   sym.deregister_tm_clones
0x000010a0    4 57   -> 51   sym.register_tm_clones
0x000010e0    5 65   -> 55   sym.__do_global_dtors_aux
0x00001130    1 9            entry.init0
0x00001000    3 27           sym._init
0x000011f0    1 5            sym.__libc_csu_fini
0x000011f8    1 13           sym._fini
0x00001180    4 101          sym.__libc_csu_init
0x00001139    4 56           main
0x00001030    1 6            sym.imp.printf
Enter fullscreen mode Exit fullscreen mode

And now Rust:

[0x00005070]> afl                                                                                                                     
0x00005070    1 46           entry0
0x000050a0    4 41   -> 34   sym.deregister_tm_clones
0x000050d0    4 57   -> 51   sym.register_tm_clones
0x00005110    5 65   -> 55   sym.__do_global_dtors_aux
0x00005160    1 9            entry.init1
0x00005170    1 73           sym.core::ptr::swap_nonoverlapping::h0b628f72a38de28a
0x00005280    9 390          sym.core::ptr::swap_nonoverlapping_bytes::h3146b1d039cc328b
0x00005410    2 56           sym.core::ptr::read::hc0647f0b104ddcd7 
0x000058d0    1 23           sym.core::intrinsics::copy_nonoverlapping::hf54e56616c7f09bb
0x00005060    1 6            sym.imp.memcpy
0x00005450    1 5            sym.core::ptr::write::h2dc6eb49417f917c
0x00005460    6 234  -> 232  sym.rust::main::h50ca6e0e5d556d08
0x000055e0    3 36           sym.core::ops::function::FnOnce::call_once::hd07bdbc3dee57ea7
0x00005810    1 24           sym.std::rt::lang_start::__u7b__u7b_closure_u7d__u7d_::hade85c7e42e345c4
0x000058f0    2 21           sym.std::sys_common::backtrace::__rust_begin_short_backtrace::hc26e3792fed3e856
0x00005620    1 5            sym.core::ops::function::FnOnce::call_once::hf0b0e9236042f757
0x000055a0    1 6            sym.core::hint::black_box::he545a54f97468964
0x00005650    1 18           sym.____as_std::process::Termination_::report::h756ba7f59c6d3eb8
0x00005670    1 24           sym._std::process::ExitCode_as_std::process::Termination_::report::h29bac469464e3ab4
0x000055b0    1 4            sym.std::sys::unix::process::process_common::ExitCode::as_i32::hd9b02696bb3467f8
0x00005840    1 30           sym.core::ptr::mut_ptr::__impl_mut_T_::offset::h34aca3bdf460d297
0x00005860    1 8            sym.core::mem::swap::h825f171a242ac571 
0x000051c0    7 147          sym.core::ptr::swap_nonoverlapping_one::h1f8a158177840f94
...
...
...
0x00035880    1 41           dbg.__umodti3
0x000155c0   23 409          dbg.write_all
0x00032450    1 17           sym._core::fmt::Formatter_as_core::fmt::Write_::write_char::h5ffc17ea9f3401f8
0x000055c0    1 25           sym.core::ops::function::FnOnce::call_once_u7b__u7b_vtableshim_u7d__u7d_::h2835ec61ce33964a
0x0002a700    5 47           sym.gimli::read::unit::allow_section_offset::h3a146150e0b83c6c
0x00024f90    3 71           dbg.__rust_panic_cleanup
0x00030010    1 28           dbg.capacity_overflow
0x00030900    1 129          sym._core::panic::Location_as_core::fmt::Display_::fmt::h2806884b93506cca
0x0001c9a0    1 16           sym._std::sys::unix::stdio::Stderr_as_std::io::Write_::write_vectored::h68f8397d34170af8
0x00030d20   15 423          sym.core::fmt::builders::DebugStruct::field::h55f88ab80a286911
0x00032330    1 17           sym.core::fmt::Formatter::write_str::hdea8fcddf3f42e36
0x000308c0    1 8            dbg.payload
0x00028cc0    1 34           sym.gimli::read::abbrev::Abbreviations::empty::h4e62cb6d5087096c
0x000146d0    9 247          sym.std::ffi::c_str::__impl_core::convert::From_std::ffi::c_str::NulError__for_std::io::error::Error_::from::hd960f59c9b6ca178
0x00025420    1 22           sym.miniz_oxide::inflate::core::DecompressorOxide::new::h217184051dfa7275
0x00015080   12 609  -> 408  sym._std::io::error::Error_as_core::fmt::Display_::fmt::hc5887a40042410b2
0x0001ac50   20 511  -> 509  dbg.rust_panic_with_hook
0x0002a370    3 213          sym.gimli::read::abbrev::Abbreviation::new::hae8a80771f27fa33
0x00014550    1 19           sym._ZN242__LT_std..error.._LT_impl_u20_core..convert..From_LT_alloc..string..String_GT__u20_for_u20_alloc..boxed..Box_LT_dyn_u2
0x00005950    1 5            sym.__rust_realloc
0x0002fe50    3 83           dbg.try_demangle
0x000355f0    8 219          sym._core::str::error::Utf8Error_as_core::fmt::Debug_::fmt::h29a13bbd67b75032
0x000306f0    1 26           dbg.fmt
0x000147d0   14 345          dbg._open
0x00032420    1 35           sym.core::fmt::Formatter::debug_list::h5675ff20deee89df
0x00035990    1 41           dbg.__udivti3
0x00032f40    1 116          dbg.len_mismatch_fail
0x00032fc0   61 605  -> 588  dbg.from_utf8
0x000306d0    1 26           sym._core::cell::BorrowError_as_core::fmt::Debug_::fmt::hadcdbfef1e44139a
0x000308d0    1 5            dbg.message
0x00005030    1 6            sym.imp.__tls_get_addr
0x00005040    1 6            sym.imp.__fxstat64
0x00005050    1 6            sym.imp._Unwind_Resume
Enter fullscreen mode Exit fullscreen mode

Like with the string comparison, we see a great deal more functions found in the Rust binary than in the C binary. This is seemingly also represented when we look at each file's size. The C binary is 16.2 kb, and the Rust binary is 3.1 mb. Next we will dissassemble the main function of each binary. C first:

[0x00001040]> s main
[0x00001139]> pdf
            ; DATA XREF from entry0 @ 0x1061
┌ 56: int main (int argc, char **argv, char **envp);
│           ; var signed int64_t var_4h @ rbp-0x4
│           0x00001139      55             push rbp
│           0x0000113a      4889e5         mov rbp, rsp
│           0x0000113d      4883ec10       sub rsp, 0x10
│           0x00001141      c745fc000000.  mov dword [var_4h], 0
│       ┌─< 0x00001148      eb1a           jmp 0x1164
│       │   ; CODE XREF from main @ 0x1168
│      ┌──> 0x0000114a      8b45fc         mov eax, dword [var_4h]
│      ╎│   0x0000114d      89c6           mov esi, eax
│      ╎│   0x0000114f      488d3dae0e00.  lea rdi, str.hello_friend___d__n ; 0x2004 ; "hello friend [%d]\n" ; const char *format
│      ╎│   0x00001156      b800000000     mov eax, 0
│      ╎│   0x0000115b      e8d0feffff     call sym.imp.printf         ; int printf(const char *format)
│      ╎│   0x00001160      8345fc01       add dword [var_4h], 1
│      ╎│   ; CODE XREF from main @ 0x1148
│      ╎└─> 0x00001164      837dfc04       cmp dword [var_4h], 4
│      └──< 0x00001168      7ee0           jle 0x114a
│           0x0000116a      b800000000     mov eax, 0
│           0x0000116f      c9             leave
└           0x00001170      c3             ret
Enter fullscreen mode Exit fullscreen mode

And now Rust:

[0x00005070]> s main
[0x00005550]> pdf
            ; DATA XREF from entry0 @ 0x5091
┌ 29: int main (int argc, char **argv);
│           ; arg int argc @ rdi
│           ; arg char **argv @ rsi
│           0x00005550      50             push rax
│           0x00005551      4863c7         movsxd rax, edi             ; argc
│           0x00005554      488d3d05ffff.  lea rdi, [sym.rust::main::h50ca6e0e5d556d08] ; 0x5460 ; "H\x81\xec\x88"
│           0x0000555b      48893424       mov qword [rsp], rsi        ; argv
│           0x0000555f      4889c6         mov rsi, rax
│           0x00005562      488b1424       mov rdx, qword [rsp]
│           0x00005566      e855020000     call sym std::rt::lang_start::h7eb4adb9a8303388 ; sym.std::rt::lang_start::h7eb4adb9a8303388
│           0x0000556b      59             pop rcx
└           0x0000556c      c3             ret
Enter fullscreen mode Exit fullscreen mode

The C program looks about as we expected, we see a loop and the call to printf and the string. The Rust program lacks all of this however, instead it just appears to set some values and call sym.std::rt::lang_start::h7eb4adb9a8303388. In fact, this main functions looks exactly like the main function of a hello world program in Rust:

[0x00005060]> s main
[0x00005360]> pdf
            ; DATA XREF from entry0 @ 0x5081
┌ 29: int main (int argc, char **argv);
│           ; arg int argc @ rdi
│           ; arg char **argv @ rsi
│           0x00005360      50             push rax
│           0x00005361      4863c7         movsxd rax, edi             ; argc
│           0x00005364      488d3db5ffff.  lea rdi, [sym.hello::main::h391fdbb81e062d23] ; 0x5320 ; "H\x83\xec8H\x8d\x05\x9d\xe2\x03"
│           0x0000536b      48893424       mov qword [rsp], rsi        ; argv
│           0x0000536f      4889c6         mov rsi, rax
│           0x00005372      488b1424       mov rdx, qword [rsp]
│           0x00005376      e8f5fdffff     call sym std::rt::lang_start::hdeef38b43c3849e1 ; sym.std::rt::lang_start::hdeef38b43c3849e1
│           0x0000537b      59             pop rcx
└           0x0000537c      c3             ret
Enter fullscreen mode Exit fullscreen mode

One small difference is in the line lea rdi, [sym.hello::main::h391fdbb81e062d23]. If we seek to this and analyse it here's what we see:

[0x00005360]> s sym.hello::main::h391fdbb81e062d23 
[0x00005320]> pdf
            ; DATA XREF from main @ 0x5364
┌ 57: sym.hello::main::h391fdbb81e062d23 ();
│           ; var int64_t var_8h @ rsp+0x8
│           0x00005320      4883ec38       sub rsp, 0x38               ; hello::main::h391fdbb81e062d23
│           0x00005324      488d059de203.  lea rax, [0x000435c8]
│           0x0000532b      488d0dde0c03.  lea rcx, [0x00036010]       ; int64_t arg4
│           0x00005332      31d2           xor edx, edx
│           0x00005334      4189d0         mov r8d, edx                ; int64_t arg5
│           0x00005337      488d7c2408     lea rdi, [var_8h]           ; int64_t arg1
│           0x0000533c      4889c6         mov rsi, rax                ; int64_t arg2
│           0x0000533f      ba01000000     mov edx, 1                  ; int64_t arg3
│           0x00005344      e897ffffff     call sym core::fmt::Arguments::new_v1::h47af78dfb834d286 ; sym.core::fmt::Arguments::new_v1::h47af78dfb834d286
│           0x00005349      488d7c2408     lea rdi, [var_8h]
│           0x0000534e      ff155c080400   call qword [dbg._print]     ; [0x45bb0:8]=0x152d0 dbg._print
│           0x00005354      4883c438       add rsp, 0x38
└           0x00005358      c3             ret
Enter fullscreen mode Exit fullscreen mode

This looks slightly more like what we would expect to see. It looks like a Rust binary puts the logic of the main function into a different function and uses the main function to setup the language. Notice the main function itself doesn't call this function, it just loads its address. This address is likely used at some point by the lang_start function. What if we look at the original Rust binary with this in mind?

[0x00005550]> s sym.rust::main::h50ca6e0e5d556d08                                                                                     
[0x00005460]> pdf                                                                                                                                                                                                               
            ; DATA XREF from main @ 0x5554                                                                                                                                                                                      
┌ 232: sym.rust::main::h50ca6e0e5d556d08 ();                                                                                          
│           ; var int64_t var_8h @ rsp+0x8                                                                                            
│           ; var int64_t var_10h @ rsp+0x10                                                                                                                                                                                                                                 
│           ; var int64_t var_18h @ rsp+0x18                                                                    
│           ; var int64_t var_1ch @ rsp+0x1c                                                                    
│           ; var int64_t var_20h @ rsp+0x20                                                                    
│           ; var int64_t var_24h @ rsp+0x24                                                                    
│           ; var int64_t var_28h @ rsp+0x28                                                                    
│           ; var int64_t var_2ch @ rsp+0x2c                                                                    
│           ; var int64_t var_30h @ rsp+0x30                                                                    
│           ; var int64_t var_34h @ rsp+0x34                                                                                          
│           ; var int64_t var_3ch @ rsp+0x3c                                                                                                                                                                                                                                 
│           ; var int64_t var_40h @ rsp+0x40                                                                    
│           ; var int64_t var_70h @ rsp+0x70                       
│           ; var int64_t var_78h @ rsp+0x78                                                                    
│           ; var int64_t var_80h @ rsp+0x80                                                                                                                                                                                    
│           0x00005460      4881ec880000.  sub rsp, 0x88               ; rust::main::h50ca6e0e5d556d08                                                                  
│           0x00005467      c74424200000.  mov dword [var_20h], 0                                               
│           0x0000546f      c74424240500.  mov dword [var_24h], 5                                               
│           0x00005477      8b7c2420       mov edi, dword [var_20h]    ; int64_t arg1                                                                                   
│           0x0000547b      8b742424       mov esi, dword [var_24h]    ; int64_t arg2                                                                                                                                           
│           0x0000547f      e89c020000     call sym <I as core::iter::traits::collect::IntoIterator>::into_iter::hb5b6e3b702ef8964 ; sym._I_as_core::iter::traits::collect::IntoIterator_::into_iter::hb5b6e3b702ef8964         
│           0x00005484      8944241c       mov dword [var_1ch], eax                                             
│           0x00005488      89542418       mov dword [var_18h], edx                                             
│           0x0000548c      8b44241c       mov eax, dword [var_1ch]                                                                                                                                                             
│           0x00005490      89442428       mov dword [var_28h], eax                                             
│           0x00005494      8b4c2418       mov ecx, dword [var_18h]                                             
│           0x00005498      894c242c       mov dword [var_2ch], ecx                                                                                                                                                                                                          
│           ; CODE XREF from rust::main::h50ca6e0e5d556d08 @ 0x5545                                             
│       ┌─> 0x0000549c      488d7c2428     lea rdi, [var_28h]          ; int64_t arg1                                                                                   
│       ╎   0x000054a1      e8ea010000     call sym core::iter::range::_<impl core::iter::traits::iterator::Iterator for core::ops::range::Range<A>>::next::h7baf9d842a7aec49 ; sym.core::iter::range::__impl_core::iter::traits::iterator::Iterator_for_core::ops::range::Range_A__::next::h7baf9d842a7aec49
│       ╎   0x000054a6      89542434       mov dword [var_34h], edx                                             
│       ╎   0x000054aa      89442430       mov dword [var_30h], eax                                                                                                                                                             
│       ╎   0x000054ae      8b442430       mov eax, dword [var_30h]                                                                                                                                                             
│       ╎   0x000054b2      89c1           mov ecx, eax                                                                                                                                                                         
│       ╎   0x000054b4      4885c9         test rcx, rcx                                                                                                                                                                        
│      ┌──< 0x000054b7      7404           je 0x54bd                                                                                                                                                                            
│     ┌───< 0x000054b9      eb00           jmp 0x54bb                                                           
│     ││╎   ; CODE XREF from rust::main::h50ca6e0e5d556d08 @ 0x54b9                                                                                                                                                             
│    ┌└───> 0x000054bb      eb0a           jmp 0x54c7             
│    │ │╎   ; CODE XREF from rust::main::h50ca6e0e5d556d08 @ 0x54b7
│    │ └──> 0x000054bd      4881c4880000.  add rsp, 0x88     
│    │  ╎   0x000054c4      c3             ret                                                                                        
..                                                                 
│    │  ╎   ; CODE XREF from rust::main::h50ca6e0e5d556d08 @ 0x54bb                                                                   
│    └────> 0x000054c7      488d3542fa02.  lea rsi, [sym.core::fmt::num::imp::__impl_core::fmt::Display_for_i32_::fmt::h0b6e831c45375ee5] ; 0x34f10 ; "SH\x83\xec0I\x89\xf2Hc7I\x89\xf3I\xf7\xdbL\x0fL\u07b8'" ; int64_t arg2                                                                                                                    
│       ╎   0x000054ce      8b442434       mov eax, dword [var_34h]                                                                   
│       ╎   0x000054d2      8944243c       mov dword [var_3ch], eax                                                                   
│       ╎   0x000054d6      488d4c243c     lea rcx, [var_3ch]                                                                                                                                                                                                                
│       ╎   0x000054db      48898c248000.  mov qword [var_80h], rcx
│       ╎   0x000054e3      488bbc248000.  mov rdi, qword [var_80h]    ; int64_t arg1                  
│       ╎   0x000054eb      e840020000     call sym core::fmt::ArgumentV1::new::h8e86b245ce5c68c3 ; sym.core::fmt::ArgumentV1::new::h8e86b245ce5c68c3
│       ╎   0x000054f0      4889442410     mov qword [var_10h], rax
│       ╎   0x000054f5      4889542408     mov qword [var_8h], rdx                  
│       ╎   0x000054fa      488d0597e003.  lea rax, sym..data.rel.ro   ; 0x43598                                                                                        
│       ╎   0x00005501      488b4c2410     mov rcx, qword [var_10h]                 
│       ╎   0x00005506      48894c2470     mov qword [var_70h], rcx                 
│       ╎   0x0000550b      488b542408     mov rdx, qword [var_8h]                  
│       ╎   0x00005510      4889542478     mov qword [var_78h], rdx                 
│       ╎   0x00005515      488d742470     lea rsi, [var_70h]                       
│       ╎   0x0000551a      488d7c2440     lea rdi, [var_40h]          ; int64_t arg1                                                                                   
│       ╎   0x0000551f      48893424       mov qword [rsp], rsi                     
│       ╎   0x00005523      4889c6         mov rsi, rax                ; int64_t arg2                                                                                   
│       ╎   0x00005526      ba02000000     mov edx, 2                  ; int64_t arg3                                                                                   
│       ╎   0x0000552b      488b0c24       mov rcx, qword [rsp]        ; int64_t arg4                                                                                   
│       ╎   0x0000552f      41b801000000   mov r8d, 1                  ; int64_t arg5                                                                                   
│       ╎   0x00005535      e846020000     call sym core::fmt::Arguments::new_v1::hdf602f2f66708cf8 ; sym.core::fmt::Arguments::new_v1::hdf602f2f66708cf8
│       ╎   0x0000553a      488d7c2440     lea rdi, [var_40h]                       
│       ╎   0x0000553f      ff157b060400   call qword [dbg._print]     ; [0x45bc0:8]=0x15880 dbg._print                                                                 
└       └─< 0x00005545      e952ffffff     jmp 0x549c 
Enter fullscreen mode Exit fullscreen mode

We see some similarities to the 'hello world' binary, noticeably the call to fmt. We also see a call to iter::range. I think it is safe to assume that this function is where the actual logic of main gets put in a Rust binary.

In conclusion, Rust is a more complex language than C, and this shows in the binaries each produce. The C binary is simpler, containing only what we wrote into the program. The Rust binary is a lot larger and contains a lot more. Despite this, Rust at the very least remains highly competitive with C on speed. I did run a few more tests and found that C was mostly faster in them, which is what I expected. It seems the result that showed Rust being faster earlier on was an anomaly. I expect that with larger programs the speed difference would likely be closer as the effect of all the setup 'cruft' would become less noticeable for Rust.

Discussion (2)

pic
Editor guide
Collapse
pgradot profile image
Pierre Gradot • Edited

Hey!

A quick observation based only on the source code is that the Rust code is more succinct. Achieving the same result in slightly fewer lines of code.

The real only different is the line for including of stdio.h because you can declare i inside the for() and save a line.

at least with this simple program

this is a difficult comparison to make given how simple the programs are.

a very simple program and may not be indicative of the speed comparison with larger and more complex program, but the result is interesting nonetheless

This is exactly the point I wanted to raise: your programs are too simple. You did a lot a work here and you will probably reuse the procedure on bigger programs when you may get some insights.

"The result is interesting nonetheless" ? Well, I unfortunately don't think so.

I have worked during many years with an embedded Java framework (made by my former employer microej.com/). A simple "hello, world" program used much more memory in Java than in C. But the experience proved that for larger programs, the size is more or less the same in C or in Java. What I've learned is that you cannot compare simple program easily, other that algorithm to perform mathematic operations. I/O to the standard output is not part of that.

Furthermore, I am not completely sure that you are comparing the same things.

By default, gcc builds with optimization level set to O1 (optimize a little). By default, cargo builds with no optimization, so I would assume that your Rust program is compiled with debug information. afl shows a lot of symbols whose names start with "dbg", confirming my supposition.

Hence, it is possible that you are in fact comparing an optimized C program with a debug Rust program. And they can't be compared with honesty. Can you check if I am right or wrong by forcing the release mode of rustc? I am very unfamiliar with rust so I can't help you...

Collapse
davidedelpapa profile image
Davide Del Papa

That is what I also wanted to point out.
rustc without arguments creates a debugging executable, while the usual release is rustc -O rust.rs