Performance Evaluation of C/C++, MicroPython, Rust and TinyGo Programming Languages on ESP32 Microcontroller

: The rapid growth of the Internet of Things (IoT) and its applications requires high computational efﬁciency, low-cost, and low-power solutions for various IoT devices. These include a wide range of microcontrollers that are used to collect, process, and transmit IoT data. ESP32 is a microcontroller with built-in wireless connectivity, suitable for various IoT applications. The ESP32 chip is gaining more popularity, both in academia and in the developer community, supported by a number of software libraries and programming languages. While low-and middle-level languages, such as C/C++ and Rust, are believed to be the most efﬁcient, TinyGo and MicroPython are more developer-friendly low-complexity languages, suitable for beginners and allowing more rapid coding. This paper evaluates the efﬁciency of the available ESP32 programming languages, namely C/C++, MicroPython, Rust, and TinyGo, by comparing their execution performance. Several popular data and signal processing algorithms were implemented in these languages, and their execution times were compared: Fast Fourier Transform (FFT), Cyclic Redundancy Check (CRC), Secure Hash Algorithm (SHA), Inﬁnite Impulse Response (IIR), and Finite Impulse Response (FIR) ﬁlters. The results show that the C/C++ implementations were fastest in most cases, closely followed by TinyGo and Rust, while MicroPython programs were many times slower than implementations in other programming languages. Therefore, the C/C++, TinyGo, and Rust languages are more suitable when execution and response time are the key factors, while Python can be used for less strict system requirements, enabling a faster and less complicated development process.


Introduction
The increasingly widespread IoT applications related to the development of various embedded systems and signal processing tasks require specialized hardware. This technical equipment must be characterized by small dimensions, low energy consumption, efficient memory use, and sufficient performance for the implementation of different signal processing functions. The main role in this case is played by various microcontrollers, which usually collect data from sensors and end-user devices, process those data, and forward results to higher-level systems. Currently, the market offers a whole range of specialized signal processing microcontrollers specially adapted for IoT tasks. One of the popular choices has become the ESP32 microcontroller, which is attractive to developers due to its technical characteristics and good software support, as well as the ability to use various programming languages. As concluded by [1], ESP32 is an excellent option for IoT devices due to the price and performance achieved by a dual core structure and a significant extension of operational features.
Recent scientific publications have proven that ESP32 chips are widely used in various fields. Aghenta and Iqbal proposed several SCADA systems [2,3] that use the ESP32 microcontroller for sensor data processing and brokering. Allafi and Iqbal [4] used ESP32 for the implementation of a low-cost web server to monitor and collect real-time photovoltaic data. Carducci et al. [5] utilized the ESP32 microcontroller for the implementation of a building This study explores, for the first time, the execution performance of the quite popular Rust and TinyGo languages and compares them with widely used C and Python (MicroPython). The present study is expected to contribute to our understanding of the suitability of the programming languages available for the development of IoT systems using the ESP32 platform. The results of the study are important both for the developers of IoT systems and academia, which use ESP32 microcontrollers extensively for research and teaching purposes. They show that programs written in the C programming language are fastest in most cases, but this advantage is not very great compared to TinyGo-and Rust-written programs. Moreover, in some cases, C programs are outperformed by both C and Rust implementations.
This article is organized as follows. Section 2 presents the performance evaluation methodology and experimental setup. Section 3 shows the results of the execution comparison of digital signal processing algorithms implemented in selected programming languages. Section 4 discusses the results and compares them with previous research.

Programming Language Features
The most important aspect when choosing a microcontroller programming language is the support of its hardware and peripherals, such as GPIO pins and communication modules like WiFi, Bluetooth, or SPI. Microcontroller vendors almost always offer hardware abstraction libraries (HALs) to access specific registers and peripherals. Without such libraries, even an otherwise very powerful language is not useful for development on a chosen platform, in this case, ESP32.
Another aspect is the support for the language itself on a device. It is not uncommon that more advanced features of a high-level programming language are limited or not supported at all on certain platforms.
Memory management is also an important feature of any programming language, as it is closely related to overall safety and performance of any project developed in that language. The three most common memory management types are automatic, manual, and garbage collector-based. In automatic management, the memory is allocated by the compiler without an explicit instruction from the programmer, mainly occurring with stack management. In contrast, manual memory management requires the programmer to do all the hard work of allocating and deallocating memory. This is usually used for a heap. Finally, with garbage collection, memory allocation can be manual, but deallocation is performed automatically at certain intervals. While manual memory management can introduce bugs and safety issues, garbage collection comes with a performance penalty and unpredictability, since the runtime environment must stop the execution of the actual code to search for memory that is not in use. This is especially important in real-time applications, where execution times must be well known and controlled.
It is Important to consider what compiler and, by extension, toolchain will be used to compile the code for ESP32. In embedded environments, the ability to optimize code size is usually important. For example, GNU Compiler Collection (GCC) offers one optimization level of size (-Os), while Low Level Virtual Machine (LLVM) compiling technology offers two optimization levels (-Os and -Oz). The alternative is an interpreted language, which allows one to immediately run the code on a platform with a suitable interpreter.
Finally, a programming language runtime system handles tasks that include setting and managing stack and heap, handling garbage collection, threading, and other dynamic features available in that language [17]. The features of runtime are provided by the standard library of the language (or interpreter for an interpreted language) and, by extension, an operating system (typically, a real-time operating system on embedded hardware). The so called bare-metal runtime is also possible, where no OS, or even standard library support, is available; however, it is usually limited in features.

Programming Languages Used for Evaluation
This paper evaluates four programming languages available for ESP32: C/C++, Rust, TinyGo, and MicroPython. Each language represents a different set of important features relevant to embedded and real-time programming, which are provided in Table 1. The C language was developed in 1978 by Brian Kernighan and Dennis Ritchie [18]. Considered the standard language in low-level system programming, it is also usually the default choice for embedded programming, since most microcontroller vendors provide tools for their products primarily in C. It is no exception with ESP32, since its vendor Espressif provides a development environment (ESP-IDF) and multiple libraries for interacting with hardware in C. It offers full support for ESP32 by the vendor via hardware abstraction libraries. It is also used in the popular Arduino framework extension for the ESP platform. In the context of embedded programming, C++ can be considered an extension of C (sometimes called C with classes) and is generally supported by vendor-provided tools (i.e., it is possible to use C++ on ESP32), but most available libraries and frameworks use only C. Most of the low-level C functionality is provided by its standard library. The C language is relatively low level, has manual memory management (for heap allocation), and is weakly typed. On ESP32, the C runtime environment includes FreeRTOS [19], which is embedded in ESP-IDF (and other development environments that use ESP-IDF, like Arduino [20]). While in principle it is possible to fully disable the real-time operating system (RTOS), it is not the standard use case (no support for this is offered by the vendor, or FreeRTOS itself), and was not considered for this paper.
Rust was originally developed by Graydon Hoare and later overtaken by Mozilla as a community-driven project [21]. Rust 1.0 was released in 2015 and since then has grown in popularity as a multipurpose programming language. A quite unusual feature of Rust is its fully automatic memory management without using garbage collection. All memory allocations are managed during compilation time, making many programming mistakes (which can easily be left in the C code) impossible [22]. Rust's ecosystem Cargo offers features such as building benchmarking and documentation generation [23]. Like C, Rust provides a large part of its functionality via the standard library (called crate in Rust). For ESP32 it is also possible to build Rust projects using the bare-metal environment, which does not use the standard library. However, this restricts the languages features (e.g., heap allocation or stack overflow protection is not supported, external custom libraries are needed) [24]. There is extensive Rust binding for ESP-IDF (esp-idf-sys crate) [25], which provides a high support for ESP32 features. However, support for ESP32 in Rust is in the so-called "Tier 3" [26], which does not guarantee that the project will build and work correctly.
TinyGo [27] is a recent version of the Go programming language (created at Google in 2007) that is oriented to embedded systems. Since Go uses an LLVM compiler like Rust, its ecosystem is also quite similar. The language is designed to be easy to parse and, by extension, easy to manipulate. Its garbage collector is predictable and easy to see for a programmer [28]. The TinyGo community lists such advantages over Rust as built-in support for concurrency without the need to rely on an RTOS-like framework, and architecturally better support for bare-metal applications [29]. As indicated in [30], TinyGo uses a cooperative scheduler and does not preempt tasks such as RTOS. Currently, support for ESP32 is not complete, and interfaces such as WiFi, Bluetooth, and even ADC are not available. Furthermore, not all standard library packages (Go library version) are supported [31]. Access to available peripherals is provided through the machine package.
MicroPython is an interpreter of Python 3 for microcontrollers and was developed by Damien George in 2014. Python is an interpreted language with garbage collector and is often perceived as a scripting language. It is widely used for application programming, especially by inexperienced programmers, due to its relative ease of use. The interpreter must be flashed into the supported microcontroller first. Some vendors now include the MicroPython interpreter as a default development environment in their products [32] as an easier alternative for beginners. MicroPython includes a subset of Python functions and libraries that are optimized for limited embedded environments [33]. MicroPython aims to be as compatible with normal Python as possible to allow an easy migration of desktop code to a microcontroller or embedded system. On ESP32, many peripherals are supported, including WiFi [34]. Since ESP32 does not usually contain a MicroPython interpreter, it can be downloaded and flashed following the instructions on the creator's website [35]. Python code can be uploaded and interpreted dynamically through a serial interface, or stored on microcontroller flash memory and run at boot time.

Algorithms Used for Performance Comparison
The ideal choice for performance comparison and evaluation would be to use an already existing comprehensive benchmark suite which includes a wide selection of different algorithms. However, few exist that are specifically targeted at embedded systems, and none exist that would consider relatively new languages like Rust or TinyGo. Existing embedded-oriented benchmarks include Bristol Energy Efficiency Benchmark Suite (BEEBS) benchmark, aimed at evaluating the energy consumption of embedded processors [36], MiBench [37], and EEMBC suite [38]. They all categorize used algorithms into different application categories, such as security, automotive, network, telecommunication, etc. These benchmarks test different types of embedded system applications in real-life use. A subset of algorithms was chosen from the benchmark suites mentioned above while considering these aspects:

•
Presence in more than one embedded-oriented benchmark suite and more than two test categories. Algorithms that were already used in several benchmarks and grouped into different test categories were preferred, as they are known to be suitable for a more comprehensive performance evaluation. • Presence in related works. Algorithms that were already used in similar performance comparisons on ESP32 were considered to be better tested and well suited for this work. Currently, the authors of [10] compare CRC-32 and SHA-256 in C and MicroPython on ESP32. • Availability in vendor libraries. Algorithms that are implemented in Espressif (ESP32 vendor) officially provided libraries were assumed to be well tested and suited for ESP32, as well as faster to implement and port to other languages, due to their comprehensive documentation and use examples.

•
Ease of use and verification. Since each selected algorithm had to be implemented in four different languages, it was crucial to be able to verify that each version outputs the correct results. Algorithms that can take a simple stream of data (such as an array) and similarly output another stream of data or a single value were preferred. Then the input and expected output data could be easily generated and verified. • Open source. The algorithm code should be available as an open source in any of the compared languages.
Five algorithms were chosen for comparison: popular hash functions CRC-32 and SHA-256, and three signal processing functions, FFT, IIR, and FIR. Many well-tested open source implementations of these functions can easily be found. The reasons for the selection of each algorithm are given in Table 2.
The CRC-32 or 32-bit Cyclic Redundancy Check is used in data integrity checks, while the SHA-256 (256-bit Secure Hash Algorithm) is used in authentication, encryption algorithms, and even cryptocurrencies. They take a byte stream for input. These functions were chosen to test how relatively simple operations perform on ESP32 while compiled in different languages, since their implementation involves simple bitwise shifting, logical, and arithmetic operations. They are used in two embedded-oriented benchmarks, the Bristol Energy Efficiency Benchmark Suite (BEEBS) and MiBench, and fit into the network, telecommunication, and security categories.
Another set of functions for this test were signal processing functions: Fast Fourier Transform (FFT), Infinite Impulse Response (IIR), and Finite Impulse Response (FIR) filters. EPS32 is powerful enough to be used for various signal processing tasks onboard; its vendor Espressif provides a comprehensive open source DSP library in Ansi C, and assembly and benchmark results for this library [39]. This enabled an easy comparison of other programming languages with vendor-provided code in C. To have more variety in data types, FFT with integer data points as inputs was selected, while IIR and FIR take float32. These algorithms are also used in the MiBench, BEEBS, and EEMBC benchmark suites and fit into the telecommunication, consumer, and automotive test categories.
Three of the selected algorithms can be further parameterized: CRC-32 by its polynomial, while FIR and IIR filters by their coefficients. For this work, no special considerations were made to select these parameters. Their values and amount (for FIR and IIR) were used as they appeared in the original source code or its usage examples. FIR was implemented as a 255th-order bandpass filter, while IIR was biquadratic type with five coefficients from their usage examples in the original source code. IEEE polynomial (0xEDB88320 in hexadecimal) was used for CRC-32.
The source codes for the selected functions were taken from free open sources in C (except for CRC-32, which was adapted from Go), then ported to other languages. Some functions were slightly adapted to make them stand-alone: library-wide error code definitions were removed from DSP functions; union type was removed from FFT code, as it has no close alternatives in other languages. The general structure of the code was kept as close as possible in all languages, while using some higher-level features of Rust, TinyGo, and MicroPython (e.g., using array length properties, instead of passing an additional length parameter like in C; using methods for structures). The details of each function are summarized in Table 2. MATLAB models for each function were also written. They were used to generate output data, which were transferred to the source code for each language, and used as a reference to verify the correct execution of the functions (more details in Section 2.3). The full source code with compilation and uploading instructions is provided in GitHub repository: https://github.com/ignasp/ProgLangComp_onESP32 (accessed on 16 December 2022).

Performance Comparison Methodology
While there are many benchmarking libraries available for each language (Rust toolchain even has a built-in benchmarking capability), they all greatly differ in implementation and use details. To provide a unified way to benchmark the selected functions, a simple custom benchmark library was first implemented in C and then ported to Rust, TinyGo, and Python.
To measure execution time, a timer structure (or object) and associated methods start and stop were defined, as detailed in pseudocode below: timer.tDuration = calculated with timer.tStart as a reference In each language, functions were used that return a monotonically increasing clock. They were provided by either the standard library of the language or by the vendor of the ESP32 (Espressif) library. Table 3 lists the exact functions used for each language. Next, a test function type RunFp was defined: TYPE RunFp : FUNCTION RezVerification( data_len : Integer, Timer : TimerObject/Struct) Different versions were implemented for every function tested since they all differ in the types and size of input and output data, the necessary initializations, and the cleanup. The function takes two parameters-data length, to enable testing for different input length sizes, and a timer object, to measure the execution time. It also compares the generated output data with predefined expected values and returns a RezVerification enumeration type, which indicates whether the output data match the reference result data. Figure 1 presents the RunFp function algorithm. Finally, another structure and a bench_Run method/function were defined to automate execution of the testing function for different number of iterations and input data lengths: The structure stores the test name for readability, an array of tested input data lengths, a number of iterations, a result printing type (which defines how the test results are printed to a serial interface), and a reference to the testing function type RunFp. The bench_Run method takes a Tester as a parameter and runs the referenced testing function for every defined data length and number of iterations, while printing the results of each iteration to the serial console, as detailed in Figure 2. Two ways of results printing are available: readable for quick verification, and CSV. Figure 3 shows an example of readable results in a seral terminal emulator. The results printed in CSV format can be easily transferred to another program (such as Microsoft Excel) for data consolidation and further analysis. In C programming language, CPU cycles were also measured as a reference for DPS algorithms (FFT, IIR, and FIR), which have their benchmark result in CPU cycles provided by the vendor Espressif.
The tester structure is defined in the main body of the program, and the bench_Run method is called for every algorithm tested. For this study, all tests were run for 100 iterations, with input data lengths of 0, 16, 32, 64, 128, 256, 512, and 1024. Since the code on ESP32 is executed on top of FreeRTOS (except for TinyGo), it is expected to see some variation in execution times of different iterations due to its preemptive scheduling (SysTick). No RTOS specific functions were used in the code.
It is also important to note that the tests were performed using a 160 MHz CPU frequency. While the ESP32 can work with up to 240 MHz, the default value after boot is 160 MHz, and it currently not possible to set the custom frequency in all languages (as their standard libraries do not have functions for that and 160 MHz is hardcoded), so the default 160 MHz was used, and verified by reading and printing the CPU frequency by available functions (provided in all languages).
During development, it was discovered that TinyGo fails to link the full code, with all functions and reference output data included. To solve this, each function was first compiled and executed separately, with full reference output data included (the problem was traced to the linker script, where it is indicated that constant global variables are loaded in DRAM, and not flash memory, and this should be fixed eventually [45]). After being convinced that all functions return the correct result, the final tests were executed with 32 reference output data points included in the code. Since the verification of the output data is outside of any time measurements, it is not expected to affect the results in any way.
A similar problem arose with MicroPython (the interpreter failed to load the full code), so the same solution was introduced.
Initially, different optimization levels were attempted for the compiled languages. TinyGo, compiled with levels other than -Oz (highest optimization for size), produced incorrect results (verification failed; it was asserted that this was due to functions producing results different from reference data), or caused program panic. Since a full comparison for other optimization levels could not be made, the tests were performed with -Os for C and -Oz for Rust and TinyGo.

Hardware Setup
ESP32 is a family of powerful microcontrollers, based on the Xtensa 32 bit architecture and manufactured by Espressif [46]. It has integrated WiFi Wi-Fi 802.11 b/g/n, dualmode Bluetooth version 4.2, and a variety of peripherals. ESP32 has a dual core processor with a frequency of up to 240 MHz, 520 Kilobytes of SRAM, and 16 Megabytes of flash program memory. ESP32 is supported by various popular integrated development environments (IDE), such as Arduino (for C/C++) and PlatformIO (for various languages through extensions).
Due to its relatively low price, the ESP32 is used in numerous prototyping and development boards [47], aimed both at professionals and enthusiasts. For this test, an M5 Stack Basic development kit with ESP32-D0WDQ6-V3 (Figure 4) was used. No special preparation is needed to use the kit. It is simply connected to the computer via USB and discovered as a serial device, on which programs can then be deployed by any of the available IDEs, or simply by using a vendor-provided tool (which is also used by the aforementioned IDEs) [48]. For this test, no internal or external peripherals were used.

Software Development Environments and Compilers
For code development, integrated development environments (IDE) were used. Visual Studio Code (VS Code) [49] was used for C, Rust, and TinyGo, and Thonny [50] was used for MicroPython.
Thonny is an open source Python IDE, which also allows using MicroPython via serial port. It was used on a Windows 10 machine.
VS Code is an open source multiplatform IDE developed by Microsoft. Its features can be highly customized by installing extensions, which are available for a wide variety of languages and scripts, including C/C++, Rust, and TinyGo.
VS Code with the PlatformIO extension [51] was used on a Windows 10 machine for the development of C/C++ code. PlatformIO allows development for various embedded platforms, including ESP32. The C/C++ extension was automatically installed as a dependency. A new project was created for the M5stack core, with the Arduino framework. All other configurations were handled by the extension.
For Rust, VS Code with the rust-analyzer extension [52] was used on a Debian 11 machine. Debian was chosen over Windows as it appeared to be easier to install the necessary toolchain on a Linux machine. The rust-analyzer extension only provides syntax highlight and checking; the toolchain needed to compile Rust for ESP32 was installed following instructions provided in [24]. With the development environment ready, a Rust project was created using template [53].
Finally, for TinyGo, VS Code with the TinyGo extension [54] was used on the same Debian 11 machine. The TinyGo extension automatically installs the Go extension as a dependency. As with Rust, the actual toolchain was installed separately, following [55]. Table 4 lists the specific versions of the IDE, extension, and toolchain versions installed for this study.

Results
This section presents the results of the comparison of different data and signal processing algorithms, including CRC-32, SHA-256, FFT, FIR, and IIR. The algorithms were implemented using four programming languages, namely, C, Rust, TinyGo, and MicroPython. The results of this comparison are presented in the diagrams and table below. Each algorithm has a corresponding graph that shows the average execution time for each programming language using a logarithmic scale. In addition, Table 5 shows the execution times together with the standard deviations. For this study, all tests were run for 100 iterations, with input data lengths of 0, 16, 32, 64, 128, 256, 512, and 1024 bytes. Therefore, there are eight measurements in each figure. Figure 5 presents a comparison of the average execution times of the CRC-32 algorithms. Since MicroPython average execution times are several orders of magnitude higher than times of the other programming languages, the results are presented using the logarithmic scale. As we can see in Figure 5, in most cases, the TinyGo implementation was the fastest, except for data sizes of 0, 16, and 32, where the C-based algorithm was slightly faster or equal. In all cases, the Rust implementation showed the third result, while the MicroPython program showed the worst execution times.  Figure 6 presents a comparison of the average execution times of the SHA-256 algorithms. As we can see in Figure 6, in all cases, the algorithm implemented in C language was the fastest, followed by the TinyGo and Rust programs. The TinyGo algorithm showed the second result in most cases, except for data lengths of 512 and 1024, where the Rust implementation was slightly faster. The MicroPython program again showed the worst execution times.  As we can see in Figure 7, in all cases, the algorithm implemented in the C language was the fastest, followed by the TinyGo and Rust programs, except for the function calls with zero data, which resulted in equal average times. The MicroPython program showed the worst execution times, as expected.  It is quite interesting that in this case, the algorithm implemented in the Rust language was the fastest, followed by the TinyGo and C programs, except for the function calls with zero data and data length of 16, where TinyGo showed a slightly better result. No surprise, the MicroPython algorithm was slowest again.  Figure 9 presents a comparison of the average execution times of the IIR filter algorithms. As we can see in Figure 9, in all cases, the algorithm implemented in the C language was the fastest, followed by the TinyGo and Rust programs, except for the data length of 32 samples, where the TinyGo program was only 0.1 µs faster on average. The MicroPython program showed the worst execution times again.   Table 5 shows the average execution times for different algorithm implementations, as well as the standard deviations for each selected data length. Note that TinyGo deviations are equal to zero in all cases, which can be explained by the fact that TinyGo programs do not use the operating system and are deployed directly on the hardware. Therefore, TinyGowritten programs always have the same execution time on the ESP32 platform, which is a very valuable feature from the point of view of the real-time systems developer. The MicroPython-based algorithms showed the worst execution performance, which is logical, since this language is not compiled, but interpreted, resulting in very high computational overhead. On the other hand, MicroPython (like any Python version) is a higher-level language than the other evaluated languages. Therefore, theoretically, it allows faster and easier code development, resulting in only a few lines of code. This is true for high-level system development, but it is not always the case in embedded programming, where a code developer usually needs to create an algorithm himself according to some formula, like in our study.

Discussion
The data presented in the Results section strongly correlate with some previous work [10,11], showing that MicroPython-based programs currently have much worse performance on the ESP32 platform, compared to programs written in the C programming language. On the other hand, this study allowed us, for the first time, to evaluate execution performance of two additional languages, namely, Rust and TinyGo. Both are quite popular among system developers and were created as an alternative to the C programming language, which until now has been considered a gold standard for embedded and IoT system development. Therefore, it was interesting for us to find out how good these alternatives are.
The results show that, surprisingly, the C-based algorithms, although fastest in most cases, in some cases were not the best. The C-based programs were outperformed by TinyGo in several cases: • CRC-32 implementations with data sizes of 64, 128, 256, 512, 1024; • FIR filter implementations with all data sizes, except 0 (just a function/method call with zero data); • IIR filter implementations with data size 32.
The C-based FIR algorithm was outperformed by Rust-based FIR implementation as well, with all data sizes except 0. In this case, the Rust-based FIR algorithm was the fastest, followed by TinyGo, C, and finally, MicroPython with all data lengths except 16, where TinyGo was slightly superior. In other cases, the Rust programs took the third place, except for SHA-256 with data sizes of 512 and 1024, outperforming the TinyGo algorithm by a few microseconds on average.
Summarizing the results, it can be concluded that in most cases C algorithms had the best execution times, followed by TinyGo, Rust, and MicroPython. The clear outsider in this case was MicroPython, whose execution times were thousands of times worse than implementations in other languages. However, the difference between C and TinyGo programs in many cases was only a few microseconds, which is not a very big difference for most embedded applications and IoT systems development. In addition, TinyGo-based algorithms have a very important advantage over C and other programming languages, since they always have the same execution time, i.e., their standard deviation of all execution times is zero. This is explained by the fact that TinyGo programs are deployed directly to the hardware without any operating system; therefore, nothing interferes with the execution process. This means that currently TinyGo technology is the best choice for the implementation of hard real-time systems on the ESP32 platform, where time jitter is a problem. However, it is unclear whether this feature will not be lost in the future, since Go (on which TinyGo is based) now uses asynchronously preemptible routines (as of version 1.14) [56]. These routines would eliminate the jitter-free execution advantage if they were introduced in the ESP32 TinyGo implementation as well. Besides, TinyGo is still in early stages of development [57], while Rust now fully supports its standard library on ESP32 and is more mature.
Finally, the MicroPython language, which is gaining more popularity, is not the best choice for low-level high-performance programming, since its execution times are incomparably longer than C, TinyGo, or even Rust. Therefore, the MicroPython language can be recommended for general-purpose high-level system programming, especially for teaching purposes and student projects, because it allows for faster and easier system development.

Limitations
The main limitation of this performance evaluation is that it does not include any ESP32 hardware-specific tests (such as using any peripherals). A comprehensive benchmark for a specific embedded system would be expected to evaluate the performance of accessing and using hardware peripherals as well. Nevertheless, we believe that this work provides a fair comparison of the current versions of the languages and produces results that will be relevant for a longer time. MicroPython, TinyGo, and Rust are still relatively new and developing languages, suitable for the ESP32 platform. While the general non-platform specific features are not expected to significantly change in the future, the same cannot be said about the hardware libraries.

Threats to Validity
The main threat to the validity of this work is within the selection of the algorithms to test. As discussed in Section 2.2, compiling a comprehensive benchmark suite for any platform is a non-trivial task, more so for four different programming languages. Personal bias and insufficient analysis cannot be excluded. However, we are confident that the selected algorithms provide a sufficiently comprehensive (as detailed in Table 2 "Areas of use" column), and most importantly, novel insight into performance of the compared languages on ESP32.

Conclusions
In this paper, we have presented the evaluation of the execution performance of the C/C++, MicroPython, Rust, and TinyGo programming languages on the ESP32 microcontroller. For this purpose, five widely used embedded processing algorithms were utilized: FFT, CRC-32, SHA-256, IIR filter, and FIR filter. This study is the first attempt to evaluate the execution performance of the newly emerging Rust and TinyGo programming languages. The aim of this evaluation is to find out how good these user-friendly languages are compared to the C/C++ language, which is a gold standard for embedded applications.
The results of this study reveal that, though the C/C++ programming language is widely believed to be the most efficient for embedded programming, that is not always the case. Our experiments showed that in a few cases the C/C++ algorithms were outperformed by algorithms implemented in TinyGo and Rust. Even in those cases where C/C++ implementations were faster, the difference between its execution times and that of other languages was not very significant. Moreover, the TinyGo algorithms demonstrated jitter-free execution, making this language more preferable for hard real-time applications. Therefore, TinyGo and Rust can be recommended as efficient higher-level ESP32 programming languages, which are characterized by faster and simpler programming compared to the C/C++ language. This work may be helpful for embedded software developers, researchers, and students who use the ESP32 platform for various application development and study processes and need to select the most suitable programming language which is currently available on this platform. Funding: This research received no external funding.

Conflicts of Interest:
The authors declare no conflict of interest.