<?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: Donovan</title>
    <description>The latest articles on DEV Community by Donovan (@donovandicks).</description>
    <link>https://dev.to/donovandicks</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1082662%2F67d9a1c1-4ce1-48e8-b5f7-64226a9cd6c2.jpeg</url>
      <title>DEV Community: Donovan</title>
      <link>https://dev.to/donovandicks</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/donovandicks"/>
    <language>en</language>
    <item>
      <title>Investigating Pydantic v2's Bold Performance Claims</title>
      <dc:creator>Donovan</dc:creator>
      <pubDate>Wed, 17 May 2023 18:51:49 +0000</pubDate>
      <link>https://dev.to/donovandicks/investigating-pydantic-v2s-bold-performance-claims-4aph</link>
      <guid>https://dev.to/donovandicks/investigating-pydantic-v2s-bold-performance-claims-4aph</guid>
      <description>&lt;h2&gt;
  
  
  Motivating Example
&lt;/h2&gt;

&lt;p&gt;The other day I was experimenting with an application I wrote about a year ago that relies on a set of rules to process some data. Due to some concerns with ease of use and extensibility, I decided to redesign the rule schema and use &lt;a href="https://docs.pydantic.dev/latest/" rel="noopener noreferrer"&gt;Pydantic&lt;/a&gt; to parse them instead of Python's builtin &lt;code&gt;dataclass&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Once the new schema was finished, I ran some performance tests which showed the new schema led to a &lt;strong&gt;70% decrease&lt;/strong&gt; in application performance. Part of this was due to changes in the structure of the rules, but the other aspect was switching to Pydantic. I decided to do some performance testing to see how much of a difference this change made.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pydantic Overview
&lt;/h2&gt;

&lt;p&gt;If you work with backend APIs in Python, you've probably used or heard of Pydantic, perhaps from &lt;a href="https://fastapi.tiangolo.com/" rel="noopener noreferrer"&gt;FastAPI&lt;/a&gt;. The library advertises itself as "data validation and settings management using Python type annotations" and it makes (de)serialization of data a breeze.&lt;sup&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Consider the following example using Python's builtin &lt;code&gt;dataclass&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt; 
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you pass data to this object's constructor that doesn't match the specified types, Python will still gladly create the object for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ABC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;39&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# &amp;gt; User(id='ABC', name=39)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using Pydantic instead, we will get an error&lt;sup&gt;2&lt;/sup&gt; if we try the same thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ABC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;39&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;# &amp;gt; ValidationError: 1 validation error for User
# &amp;gt; id
# &amp;gt;   value is not a valid integer (type=type_error.integer)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hopefully this illustrates one of the many benefits of using Pydantic. I invite you to read &lt;a href="https://docs.pydantic.dev/latest/usage/models/" rel="noopener noreferrer"&gt;the documentation&lt;/a&gt; to see the full capabilities of the library beyond this contrived example.&lt;/p&gt;

&lt;h2&gt;
  
  
  New Performance Claims
&lt;/h2&gt;

&lt;p&gt;Pydantic's capabilities can make your programs more resilient and easier to read and write, but you may sacrifice some performance for these benefits. To address these concerns, the creators of Pydantic endeavored to rewrite the backend "with validation and serialisation logic implemented in Rust" to boost performance by "5-50x" over &lt;code&gt;v1&lt;/code&gt; in the new major version coming soon.&lt;sup&gt;3&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;For those unfamiliar, Pydantic is currently implemented in Python and this rewrite shifts most of the code to &lt;a href="https://www.rust-lang.org/" rel="noopener noreferrer"&gt;Rust&lt;/a&gt;, a systems programming language touted as "blazingly fast" and safe. &lt;/p&gt;

&lt;p&gt;If you're familiar with Rust then these types of gains may not seem unreasonable, but "5-50x" is still a big claim, especially in the notoriously slow world of Python. I'm a big fan of Rust but I wanted to verify these claims for myself, as well as understanding how they compare to Python's builtin functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmarking
&lt;/h2&gt;

&lt;p&gt;In my original example, my main bottleneck turned out to be in data deserialization - converting a "rule" into a Pydantic object.&lt;/p&gt;

&lt;p&gt;To test this, we will setup some benchmarks using &lt;a href="https://github.com/ionelmc/pytest-benchmark" rel="noopener noreferrer"&gt;pytest-benchmark&lt;/a&gt;, some sample data with a simple schema, and compare results between Python's &lt;code&gt;dataclass&lt;/code&gt;, Pydantic &lt;code&gt;v1&lt;/code&gt;, and &lt;code&gt;v2&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;Here is the test setup which uses a simple model of a user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserDC&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserPY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;


&lt;span class="nd"&gt;@pytest.mark.benchmarks&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_dc_bench&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pedantic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UserDC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;test_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rounds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;


&lt;span class="nd"&gt;@pytest.mark.benchmarks&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_py_bench&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pedantic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UserPY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;test_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rounds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are using Python's &lt;code&gt;dataclass&lt;/code&gt; as a baseline for comparison since I will need to run these tests with two different versions of Pydantic installed.&lt;/p&gt;

&lt;p&gt;All benchmarks are run on a 2021 MacBook Pro with M1 Pro and 32GB RAM with the following environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Test session starts (platform: darwin, Python 3.11.3, pytest 7.3.1, pytest-sugar 0.9.7)
benchmark: 4.0.0 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Benchmark Results
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Using Pydantic &lt;code&gt;v1&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fukzwtjc4ydp8p2zar0ri.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fukzwtjc4ydp8p2zar0ri.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Results reproduced for visibility:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name (time in ns)&lt;/th&gt;
&lt;th&gt;Min&lt;/th&gt;
&lt;th&gt;Max&lt;/th&gt;
&lt;th&gt;Mean&lt;/th&gt;
&lt;th&gt;StdDev&lt;/th&gt;
&lt;th&gt;Median&lt;/th&gt;
&lt;th&gt;IQR&lt;/th&gt;
&lt;th&gt;Outliers&lt;/th&gt;
&lt;th&gt;OPS (Kops/s)&lt;/th&gt;
&lt;th&gt;Rounds&lt;/th&gt;
&lt;th&gt;Iterations&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;test_dc_parse_bench&lt;/td&gt;
&lt;td&gt;166.5991 (1.0)&lt;/td&gt;
&lt;td&gt;1,791.5998 (1.0)&lt;/td&gt;
&lt;td&gt;178.0631 (1.0)&lt;/td&gt;
&lt;td&gt;19.0275 (1.0)&lt;/td&gt;
&lt;td&gt;179.0992 (1.0)&lt;/td&gt;
&lt;td&gt;4.1997 (1.0)&lt;/td&gt;
&lt;td&gt;210;1391&lt;/td&gt;
&lt;td&gt;5,615.9867 (1.0)&lt;/td&gt;
&lt;td&gt;50000&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;test_py_parse_bench&lt;/td&gt;
&lt;td&gt;3,170.7998 (19.03)&lt;/td&gt;
&lt;td&gt;9,162.4999 (5.11)&lt;/td&gt;
&lt;td&gt;3,227.0666 (18.12)&lt;/td&gt;
&lt;td&gt;69.3922 (3.65)&lt;/td&gt;
&lt;td&gt;3,216.6994 (17.96)&lt;/td&gt;
&lt;td&gt;20.7001 (4.93)&lt;/td&gt;
&lt;td&gt;2192;2766&lt;/td&gt;
&lt;td&gt;309.8789 (0.06)&lt;/td&gt;
&lt;td&gt;50000&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Using Pydantic &lt;code&gt;v2&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fks9n8lr8wdowc1yyfndq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fks9n8lr8wdowc1yyfndq.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Reproduced for visibility:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name (time in ns)&lt;/th&gt;
&lt;th&gt;Min&lt;/th&gt;
&lt;th&gt;Max&lt;/th&gt;
&lt;th&gt;Mean&lt;/th&gt;
&lt;th&gt;StdDev&lt;/th&gt;
&lt;th&gt;Median&lt;/th&gt;
&lt;th&gt;IQR&lt;/th&gt;
&lt;th&gt;Outliers&lt;/th&gt;
&lt;th&gt;OPS (Mops/s)&lt;/th&gt;
&lt;th&gt;Rounds&lt;/th&gt;
&lt;th&gt;Iterations&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;test_dc_parse_bench&lt;/td&gt;
&lt;td&gt;170.8002 (1.0)&lt;/td&gt;
&lt;td&gt;1,754.0995 (1.0)&lt;/td&gt;
&lt;td&gt;183.2414 (1.0)&lt;/td&gt;
&lt;td&gt;14.7975 (1.0)&lt;/td&gt;
&lt;td&gt;183.3003 (1.0)&lt;/td&gt;
&lt;td&gt;4.2011 (1.0)&lt;/td&gt;
&lt;td&gt;542;2882&lt;/td&gt;
&lt;td&gt;5.4573 (1.0)&lt;/td&gt;
&lt;td&gt;50000&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;test_py_parse_bench&lt;/td&gt;
&lt;td&gt;741.6995 (4.34)&lt;/td&gt;
&lt;td&gt;4,591.6997 (2.62)&lt;/td&gt;
&lt;td&gt;768.6778 (4.19)&lt;/td&gt;
&lt;td&gt;21.8820 (1.48)&lt;/td&gt;
&lt;td&gt;766.6997 (4.18)&lt;/td&gt;
&lt;td&gt;8.3994 (2.00)&lt;/td&gt;
&lt;td&gt;2424;3224&lt;/td&gt;
&lt;td&gt;1.3009 (0.24)&lt;/td&gt;
&lt;td&gt;50000&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Both Pydantic &lt;code&gt;v1&lt;/code&gt; and &lt;code&gt;v2&lt;/code&gt; perform significantly slower than &lt;code&gt;dataclass&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Pydantic's performance varies more widely than &lt;code&gt;dataclass&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Pydantic &lt;code&gt;v2&lt;/code&gt; performs significantly faster than &lt;code&gt;v1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Pydantic &lt;code&gt;v2&lt;/code&gt;'s performance varies less than &lt;code&gt;v1&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Analysis
&lt;/h2&gt;

&lt;p&gt;The results here confirm my suspicion that switching from &lt;code&gt;dataclass&lt;/code&gt; to Pydantic was a significant factor in the performance degradation that sparked this investigation. We can see that, on average, Pydantic &lt;code&gt;v1&lt;/code&gt; is about 18x slower than &lt;code&gt;dataclass&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We can also see that Pydantic &lt;code&gt;v2&lt;/code&gt; is, on average, about 4x faster than &lt;code&gt;v1&lt;/code&gt; for this particular use-case. If you check &lt;a href="https://docs.pydantic.dev/latest/blog/pydantic-v2/#performance" rel="noopener noreferrer"&gt;another post&lt;/a&gt; from Pydantic, you may see the range "4-50x" instead of the aforementioned "5-50x", which &lt;em&gt;technically&lt;/em&gt; means these results meet their claims, even if just barely. I won't split hairs over it since this example is incredibly simple and not the likely target of optimization.&lt;/p&gt;

&lt;p&gt;What's important to note here is even "just" 4x performance improvements can be a major win, &lt;em&gt;especially&lt;/em&gt; if these gains can be achieved with little to no changes.&lt;sup&gt;4&lt;/sup&gt; For me, this is an easy win to recoup some of the performance losses I was facing in my initial example.&lt;/p&gt;

&lt;p&gt;I encourage you to checkout the &lt;a href="https://github.com/pydantic/pydantic-core/tree/main/tests/benchmarks" rel="noopener noreferrer"&gt;official benchmarks&lt;/a&gt; for more realistic and detailed examples, and, as always, YMMV.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;Will Pydantic's new major release live up to the hype? In most cases you will probably see improvements on the lower bound of their estimations, but, as mentioned, even this can be a big win. In my opinion, Pydantic brings a number of enhancements to Python applications that more than make up for any of its performance losses.&lt;/p&gt;

&lt;p&gt;It's worth noting that these improvements will also impact other libraries and frameworks that rely on Pydantic, such as FastAPI and &lt;a href="https://github.com/awslabs/aws-lambda-powertools-python/tree/develop" rel="noopener noreferrer"&gt;AWS Lambda Powertools&lt;/a&gt;, which could deliver some transitive performance improvements to various projects that don't directly depend on Pydantic themselves.&lt;/p&gt;

&lt;h5&gt;
  
  
  Footnotes
&lt;/h5&gt;

&lt;h6&gt;
  
  
  &lt;a href="https://docs.pydantic.dev/latest/" rel="noopener noreferrer"&gt;[1]&lt;/a&gt;
&lt;/h6&gt;

&lt;h6&gt;
  
  
  [2] This example will actually coerce &lt;code&gt;39&lt;/code&gt; into a string using Pydantic &lt;code&gt;v1&lt;/code&gt; if you resolve the type error on &lt;code&gt;id&lt;/code&gt;. Using Pydantic &lt;code&gt;v2&lt;/code&gt; will instead report a validation error for both fields.
&lt;/h6&gt;

&lt;h6&gt;
  
  
  &lt;a href="https://pydantic.dev/#v2" rel="noopener noreferrer"&gt;[3]&lt;/a&gt;
&lt;/h6&gt;

&lt;h6&gt;
  
  
  [4] See the &lt;a href="https://docs.pydantic.dev/latest/blog/pydantic-v2-alpha/#migration-guide" rel="noopener noreferrer"&gt;migration guide&lt;/a&gt; for specifics on upgrading
&lt;/h6&gt;

</description>
      <category>python</category>
      <category>pydantic</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
