<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Zhen XIN</title>
    <description>The latest articles on DEV Community by Zhen XIN (@lone0).</description>
    <link>https://dev.to/lone0</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4218230%2F89be3ba3-3a76-4ee6-9432-97f06e5eff11.jpg</url>
      <title>DEV Community: Zhen XIN</title>
      <link>https://dev.to/lone0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lone0"/>
    <language>en</language>
    <item>
      <title>Does Pascal code run slower than C code on the ESP32?</title>
      <dc:creator>Zhen XIN</dc:creator>
      <pubDate>Sat, 31 Oct 2026 04:12:42 +0000</pubDate>
      <link>https://dev.to/lone0/does-pascal-code-run-slower-than-c-code-on-the-esp32-1jp4</link>
      <guid>https://dev.to/lone0/does-pascal-code-run-slower-than-c-code-on-the-esp32-1jp4</guid>
      <description>&lt;p&gt;&lt;strong&gt;Abstract.&lt;/strong&gt; This article compares the runtime performance of a classical floating-point algorithm implemented in Pascal and in C on the ESP32-S3 microcontroller. The Pascal program initially ran seven times slower than the C program. Disassembly analysis attributes the difference to the absence of a hardware division instruction in the Xtensa FPU and to divergent strategies between the Free Pascal Compiler and GCC: the former uses software emulation (~240 cycles per division), the latter a reciprocal-based ROM routine (~40 cycles). A one-line declaration makes the ROM routine callable from Pascal, and together with two further optimizations the Pascal program ultimately outperforms the C program.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;Pascal is rarely used today, but active communities remain in niche domains. The ESP32 is a widely adopted microcontroller. The Free Pascal Compiler (FPC), the most popular open-source Pascal compiler, already includes support for the ESP-IDF SDK and the FreeRTOS operating system. How substantial is that support in practice?&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Benchmark design
&lt;/h2&gt;

&lt;p&gt;To answer this question, a classical floating-point algorithm — computing π with the Leibniz series — was implemented in Pascal and in C and executed on an ESP32-S3. A Pascal implementation of the algorithm was found in the &lt;code&gt;speed-comparison&lt;/code&gt; repository on GitHub (&lt;a href="https://github.com/niklas-heer/speed-comparison/blob/master/src/leibniz.pas" rel="noopener noreferrer"&gt;leibniz.pas&lt;/a&gt;). Its loop body is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;rounds&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loop contains only floating-point multiplication and addition, occupies very little memory, and is compute-intensive — well suited to measuring compiler efficiency. The same repository provides a C implementation for comparison.&lt;/p&gt;

&lt;p&gt;The FPU of the Xtensa core supports single precision only. All floating-point data was therefore changed to the &lt;code&gt;Single&lt;/code&gt; type: FPC maps &lt;code&gt;Real&lt;/code&gt; to &lt;code&gt;Double&lt;/code&gt;, which would silently trigger software floating point. The C implementation was adapted accordingly — C floating-point literals such as &lt;code&gt;2.0&lt;/code&gt; are &lt;code&gt;double&lt;/code&gt; and would likewise invoke the software floating point routines of libgcc, so the literals were changed to &lt;code&gt;2.0f&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Both programs were then run on the ESP32-S3 (Xtensa LX7 dual core, 512 KiB of internal RAM) for one million iterations, using default compiler settings; for C, the default optimization level of ESP-IDF (&lt;code&gt;-Og&lt;/code&gt;) was applied.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Results
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn7phkthgzb0l3rgrokqf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn7phkthgzb0l3rgrokqf.png" alt="Figure 1: initial run, Free Pascal implementation" width="345" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe7iqeqk1mdj2dqe301id.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe7iqeqk1mdj2dqe301id.png" alt="Figure 2: initial run, C implementation" width="345" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Figure 1.&lt;/strong&gt; Free Pascal implementation, one million iterations: 3533.289 ms.&lt;br&gt;
&lt;strong&gt;Figure 2.&lt;/strong&gt; C implementation (ESP-IDF, &lt;code&gt;-Og&lt;/code&gt;), one million iterations: 531.7 ms.&lt;/p&gt;

&lt;p&gt;The initial result is striking: the Pascal program took 3533 ms — seven times the runtime of the C program.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Root-cause analysis
&lt;/h2&gt;

&lt;p&gt;The disassembly of both programs was compared (with the assistance of an AI assistant), which revealed an interesting fact: the Xtensa FPU has &lt;strong&gt;no division instruction&lt;/strong&gt;. It provides 32-bit single-precision addition, subtraction, and multiplication — but not division.&lt;/p&gt;

&lt;p&gt;In the absence of a hardware division instruction, Free Pascal calls its built-in software floating-point emulation routine &lt;code&gt;float32_div&lt;/code&gt;, which costs several hundred instructions per division.&lt;/p&gt;

&lt;p&gt;GCC, as shipped with ESP-IDF, instead calls a private routine located in the ESP ROM under the name &lt;code&gt;__divsf3&lt;/code&gt;: thirty floating-point instructions that synthesize a division by computing a reciprocal, multiplying, and correcting the exponent. The AI assistant estimated software emulation at roughly 240 clock cycles per division, versus roughly 40 cycles for the reciprocal-based routine — one sixth of the cost.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Calling the ROM routine from Pascal
&lt;/h2&gt;

&lt;p&gt;FPC does not implement the reciprocal method. Can Pascal code use &lt;code&gt;__divsf3&lt;/code&gt; anyway? It can: the symbol is always present in the ROM and only needs to be declared and called by name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__divsf3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;single&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;single&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;cdecl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="s"&gt;'__divsf3'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cm"&gt;{ declare __divsf3 for the Pascal program }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;__divsf3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single declaration yields a six-fold speed-up, even though the compiler itself does not support the technique.&lt;/p&gt;

&lt;p&gt;Two further improvements were suggested:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;FPC is not aggressive about mapping variables to registers. Global variables are re-loaded and stored (&lt;code&gt;lsi&lt;/code&gt;/&lt;code&gt;ssi&lt;/code&gt;) on every access. Moving variables onto the subroutine stack as locals makes them eligible for register allocation.&lt;/li&gt;
&lt;li&gt;One subexpression of the loop, &lt;code&gt;(2 * i - 1)&lt;/code&gt;, is recomputed with shift and add instructions on every iteration; incrementing the previous value by 2 is sufficient and saves one instruction. The same technique applies to C.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The final loop body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;rounds&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                 &lt;span class="cm"&gt;{ neg.s — not mul.s by a -1.0 literal }&lt;/span&gt;
      &lt;span class="cm"&gt;{ Avoid SoftFloat float32_div; call ROM __divsf3 like GCC. }&lt;/span&gt;
      &lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;__divsf3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;denom&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="k"&gt;Inc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;denom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Results after optimization
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm7bkz53hhljiayud39m7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm7bkz53hhljiayud39m7.png" alt="Figure 3: Pascal implementation after the optimizations" width="345" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Figure 3.&lt;/strong&gt; Optimized Pascal implementation, one million iterations: 525.477 ms.&lt;/p&gt;

&lt;p&gt;The runtime improved seven-fold, and the Pascal program now outperforms the C program by 6 milliseconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Conclusion
&lt;/h2&gt;

&lt;p&gt;As a compiled language, Pascal carries very little runtime overhead and belongs to the same class as C. The quality of the generated instruction stream, however, depends on the optimization effort of the compiler back end.&lt;/p&gt;

&lt;p&gt;With respect to the current Xtensa back end of Free Pascal Compiler 3.3.1 (trunk), optimization clearly lags behind GCC — particularly given that GCC ran at a modest optimization level — in register allocation, sharing of intermediate values across statements, and loop optimization. This does not mean that Pascal is unsuitable today for MCUs such as the ESP series: the existing code quality is entirely adequate for many tasks, and where floating-point performance matters, compiler features that are not yet supported can be worked around at the application level.&lt;/p&gt;

</description>
      <category>esp32</category>
      <category>pascal</category>
      <category>embedded</category>
      <category>compilers</category>
    </item>
  </channel>
</rss>
