Congratulations!

[Valid RSS] This is a valid RSS feed.

Recommendations

This feed is valid, but interoperability with the widest range of feed readers could be improved by implementing the following recommendations.

Source: https://www.peterbe.com/rss.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Peterbe.com</title><link>https://www.peterbe.com/rss.xml</link><description>Stuff in Peter's head</description><atom:link href="https://www.peterbe.com/rss.xml" rel="self"/><language>en-us</language><lastBuildDate>Sun, 17 Mar 2024 13:58:51 +0000</lastBuildDate><item><title>How do you thousands-comma AND whitespace format a f-string in Python</title><link>https://www.peterbe.com/plog/thousands-and-whitespace-f-string-in-python</link><description>Use {my_large_integer:&lt;20,}</description><pubDate>Sun, 17 Mar 2024 13:58:51 +0000</pubDate><guid>https://www.peterbe.com/plog/thousands-and-whitespace-f-string-in-python</guid></item><item><title>Leibniz formula for π in Python, JavaScript, and Ruby</title><link>https://www.peterbe.com/plog/leibniz-formula-for-pi</link><description>Different ways to calculate the value of π  using the Leibniz formula</description><pubDate>Thu, 14 Mar 2024 20:24:13 +0000</pubDate><guid>https://www.peterbe.com/plog/leibniz-formula-for-pi</guid></item><item><title>Notes on porting a Next.js v14 app from Pages to App Router</title><link>https://www.peterbe.com/plog/porting-next-v14-pages-to-app-router</link><description>&lt;p&gt;Unfortunately, the app I ported from using the Pages Router to using App Router, is in a private repo. It's a Next.js static site SPA (Single Page App).&lt;/p&gt;
  3. &lt;p&gt;It's built with &lt;code&gt;npm run build&lt;/code&gt; and then exported so that the &lt;code&gt;out/&lt;/code&gt; directory is the only thing I need to ship to the CDN and it just works. There's a home page and a few dynamic routes whose slugs depend on an SQL query. So the SQL (PostgreSQL) connection, using &lt;code&gt;knex&lt;/code&gt;, has to be present when running &lt;code&gt;npm run build&lt;/code&gt;.&lt;/p&gt;
  4. &lt;p&gt;In no particular order, let's look at some differences&lt;/p&gt;
  5. &lt;h2&gt;Build times&lt;/h2&gt;
  6. &lt;h3&gt;With caching&lt;/h3&gt;
  7. &lt;p&gt;After running &lt;code&gt;next build&lt;/code&gt; a bunch of times, the rough averages are:&lt;/p&gt;
  8. &lt;ul&gt;
  9. &lt;li&gt;Pages Router: 20.5 seconds&lt;/li&gt;
  10. &lt;li&gt;App Router: 19.5 seconds&lt;/li&gt;
  11. &lt;/ul&gt;
  12. &lt;h3&gt;Without caching&lt;/h3&gt;
  13. &lt;p&gt;After running &lt;code&gt;rm -fr .next &amp;amp;&amp;amp; next build&lt;/code&gt; a bunch of times, the rough averages are:&lt;/p&gt;
  14. &lt;ul&gt;
  15. &lt;li&gt;Pages Router: 28.5 seconds&lt;/li&gt;
  16. &lt;li&gt;App Router: 31 seconds&lt;/li&gt;
  17. &lt;/ul&gt;
  18. &lt;h3&gt;Note&lt;/h3&gt;
  19. &lt;p&gt;I have another SPA app that is built with &lt;code&gt;vite&lt;/code&gt; and &lt;code&gt;wouter&lt;/code&gt; and uses the heavy &lt;code&gt;mantine&lt;/code&gt; for the UI library. That SPA app does a LOT more in terms of components and pages etc. That one takes 9 seconds on average.&lt;/p&gt;
  20. &lt;h2&gt;Static output&lt;/h2&gt;
  21. &lt;p&gt;If you compare the generated &lt;code&gt;out/_next/static/chunks&lt;/code&gt; there's a strange difference.&lt;/p&gt;
  22. &lt;h3&gt;Pages Router&lt;/h3&gt;
  23. &lt;pre&gt;360.0 KiB [##########################] /pages
  24. 268.0 KiB [###################       ]  726-4194baf1eea221e4.js
  25. 160.0 KiB [###########               ]  ee8b1517-76391449d3636b6f.js
  26. 140.0 KiB [##########                ]  framework-5429a50ba5373c56.js
  27. 112.0 KiB [########                  ]  cdfd8999-a1782664caeaab31.js
  28. 108.0 KiB [########                  ]  main-930135e47dff83e9.js
  29. 92.0 KiB [######                    ]  polyfills-c67a75d1b6f99dc8.js
  30. 16.0 KiB [#                         ]  502-394e1f5415200700.js
  31.  8.0 KiB [                          ]  0e226fb0-147f1e5268512885.js
  32.  4.0 KiB [                          ]  webpack-1b159842bd89504c.js&lt;/pre&gt;
  33.  
  34. &lt;p&gt;In total &lt;strong&gt;1.2 MiB&lt;/strong&gt; across 15 files.&lt;/p&gt;
  35. &lt;h3&gt;App Router&lt;/h3&gt;
  36. &lt;pre&gt;428.0 KiB [##########################]  142-94b03af3aa9e6d6b.js
  37. 196.0 KiB [############              ]  975-62bfdeceb3fe8dd8.js
  38. 184.0 KiB [###########               ]  25-aa44907f6a6c25aa.js
  39. 172.0 KiB [##########                ]  fd9d1056-e15083df91b81b75.js
  40. 164.0 KiB [##########                ]  ca377847-82e8fe2d92176afa.js
  41. 140.0 KiB [########                  ]  framework-aec844d2ccbe7592.js
  42. 116.0 KiB [#######                   ]  a6eb9415-a86923c16860379a.js
  43. 112.0 KiB [#######                   ]  69-f28d58313be296c0.js
  44. 108.0 KiB [######                    ]  main-67e49f9e34a5900f.js
  45. 92.0 KiB [#####                     ]  polyfills-c67a75d1b6f99dc8.js
  46. 44.0 KiB [##                        ] /app
  47. 24.0 KiB [#                         ]  1cc5f7f4-2f067a078d041167.js
  48. 24.0 KiB [#                         ]  250-47a2e67f72854c46.js
  49.  8.0 KiB [                          ] /pages
  50.  4.0 KiB [                          ]  webpack-baa830a732d3dbbf.js
  51.  4.0 KiB [                          ]  main-app-f6b391c808310b44.js&lt;/pre&gt;
  52.  
  53. &lt;p&gt;In total &lt;strong&gt;1.7 MiB&lt;/strong&gt; across 27 files.&lt;/p&gt;
  54. &lt;h3&gt;Notes&lt;/h3&gt;
  55. &lt;p&gt;What makes the JS bundle large is most certainly due to using &lt;code&gt;@primer/react&lt;/code&gt;, &lt;code&gt;@fullcalendar&lt;/code&gt;, and &lt;code&gt;react-chartjs-2&lt;/code&gt;.&lt;br /&gt;
  56. But why is the difference so large?&lt;/p&gt;
  57. &lt;h2&gt;Dev start time&lt;/h2&gt;
  58. &lt;p&gt;The way Next.js works, with &lt;code&gt;npm run dev&lt;/code&gt;, is that it starts a server at &lt;code&gt;localhost:3000&lt;/code&gt; and only when you request a URL does it compile something. It's essentially lazy and that's a good thing because in a bigger app, you might have too many different entries so it'd be silly to wait for all of them to compile if you might not use them all.&lt;/p&gt;
  59. &lt;h3&gt;Pages Router&lt;/h3&gt;
  60. &lt;pre&gt;❯ npm run dev
  61.  
  62. ...
  63.  
  64. ✓ Ready in 1125ms
  65. ○ Compiling / ...
  66. ✓ Compiled / in 2.9s (495 modules)&lt;/pre&gt;
  67.  
  68. &lt;h3&gt;App Router&lt;/h3&gt;
  69. &lt;pre&gt;❯ npm run dev
  70.  
  71. ...
  72.  
  73. ✓ Ready in 1201ms
  74. ○ Compiling / ...
  75. ✓ Compiled / in 3.7s (1023 modules)&lt;/pre&gt;
  76.  
  77. &lt;p&gt;Mind you, it almost always says "Ready in 1201ms" or but the other number, like "3.7s" in this example, that seems to fluctuate quite wildly. I don't know why.&lt;/p&gt;
  78. &lt;h2&gt;Conclusion&lt;/h2&gt;
  79. &lt;p&gt;&lt;strong&gt;Was it worth it?&lt;/strong&gt; Yes and no.&lt;/p&gt;
  80. &lt;p&gt;I've never liked &lt;code&gt;next/router&lt;/code&gt;. With App Router you instead use &lt;code&gt;next/navigation&lt;/code&gt; which feels much more refined and simple. The old &lt;code&gt;next/router&lt;/code&gt; is still there which exposes a &lt;code&gt;useRouter&lt;/code&gt; hook which is still used for doing &lt;code&gt;push&lt;/code&gt; and &lt;code&gt;replace&lt;/code&gt;.&lt;/p&gt;
  81. &lt;p&gt;The &lt;code&gt;getStaticPaths&lt;/code&gt; and the &lt;code&gt;getStaticProps&lt;/code&gt; were not really that terrible in Pages Router.&lt;/p&gt;
  82. &lt;p&gt;I think the whole point of App Router is that you can get external data not only in &lt;code&gt;getStaticProps&lt;/code&gt; (or &lt;code&gt;getServerSideProps&lt;/code&gt;) but you can more freely go and get external data in places like &lt;code&gt;layout.tsx&lt;/code&gt;, which means less prop-drilling.&lt;/p&gt;
  83. &lt;p&gt;There are some nicer APIs with App Router. And it's the future of Next.js and how Vercel is pushing it forward.&lt;/p&gt;</description><pubDate>Sat, 02 Mar 2024 21:04:10 +0000</pubDate><guid>https://www.peterbe.com/plog/porting-next-v14-pages-to-app-router</guid></item><item><title>How to avoid a count query in Django if you can</title><link>https://www.peterbe.com/plog/how-to-avoid-a-count-query-in-django-if-you-can</link><description>&lt;p&gt;Suppose you have a complex Django QuerySet query that is somewhat &lt;strong&gt;costly&lt;/strong&gt; (in other words slow). And suppose you want to return:&lt;/p&gt;
  84. &lt;ol&gt;
  85. &lt;li&gt;The first N results  &lt;/li&gt;
  86. &lt;li&gt;A count of the total possible results&lt;/li&gt;
  87. &lt;/ol&gt;
  88. &lt;p&gt;So your implementation might be something like this:&lt;/p&gt;
  89. &lt;pre&gt;&lt;code class="hljs"&gt;
  90. &lt;span class="hljs-keyword"&gt;def&lt;/span&gt; &lt;span class="hljs-title function_"&gt;get_results&lt;/span&gt;(&lt;span class="hljs-params"&gt;queryset, fields, size&lt;/span&gt;):
  91.    count = queryset.count()
  92.    results = []
  93.    &lt;span class="hljs-keyword"&gt;for&lt;/span&gt; record &lt;span class="hljs-keyword"&gt;in&lt;/span&gt; queryset.values(*fields)[:size]
  94.        results.append(record)
  95.    &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; {&lt;span class="hljs-string"&gt;&amp;quot;count&amp;quot;&lt;/span&gt;: count, &lt;span class="hljs-string"&gt;&amp;quot;results&amp;quot;&lt;/span&gt;: results}
  96. &lt;/code&gt;&lt;/pre&gt;
  97.  
  98. &lt;p&gt;That'll work. If there are 1,234 rows in your database table that match those specific filters, what you might get back from this is:&lt;/p&gt;
  99. &lt;pre&gt;&lt;code class="hljs"&gt;
  100. &lt;span class="hljs-meta"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;results = get_results(my_queryset, (&lt;span class="hljs-string"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;, &lt;span class="hljs-string"&gt;&amp;quot;age&amp;quot;&lt;/span&gt;), &lt;span class="hljs-number"&gt;5&lt;/span&gt;)
  101. &lt;span class="hljs-meta"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;results[&lt;span class="hljs-string"&gt;&amp;quot;count&amp;quot;&lt;/span&gt;]
  102. &lt;span class="hljs-number"&gt;1234&lt;/span&gt;
  103. &lt;span class="hljs-meta"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="hljs-built_in"&gt;len&lt;/span&gt;(results[&lt;span class="hljs-string"&gt;&amp;quot;results&amp;quot;&lt;/span&gt;])
  104. &lt;span class="hljs-number"&gt;5&lt;/span&gt;
  105. &lt;/code&gt;&lt;/pre&gt;
  106.  
  107. &lt;p&gt;Or, if the filters would only match 3 rows in your database table:&lt;/p&gt;
  108. &lt;pre&gt;&lt;code class="hljs"&gt;
  109. &lt;span class="hljs-meta"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;results = get_results(my_queryset, (&lt;span class="hljs-string"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;, &lt;span class="hljs-string"&gt;&amp;quot;age&amp;quot;&lt;/span&gt;), &lt;span class="hljs-number"&gt;5&lt;/span&gt;)
  110. &lt;span class="hljs-meta"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;results[&lt;span class="hljs-string"&gt;&amp;quot;count&amp;quot;&lt;/span&gt;]
  111. &lt;span class="hljs-number"&gt;3&lt;/span&gt;
  112. &lt;span class="hljs-meta"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="hljs-built_in"&gt;len&lt;/span&gt;(results[&lt;span class="hljs-string"&gt;&amp;quot;results&amp;quot;&lt;/span&gt;])
  113. &lt;span class="hljs-number"&gt;3&lt;/span&gt;
  114. &lt;/code&gt;&lt;/pre&gt;
  115.  
  116. &lt;p&gt;Between your Python application and your database you'll see:&lt;/p&gt;
  117. &lt;pre&gt;query 1: SELECT COUNT(*) FROM my_database WHERE ...
  118. query 2: SELECT name, age FROM my_database WHERE ... LIMIT 5&lt;/pre&gt;
  119.  
  120. &lt;p&gt;The &lt;strong&gt;problem&lt;/strong&gt; with this is that, in the latter case, you had to send &lt;strong&gt;two database queries&lt;/strong&gt; when all you needed was &lt;strong&gt;one&lt;/strong&gt;.&lt;br /&gt;
  121. &lt;em&gt;If&lt;/em&gt; you knew it would only match a tiny amount of records, you &lt;em&gt;could&lt;/em&gt; do this:&lt;/p&gt;
  122. &lt;pre&gt;&lt;code class="hljs"&gt;
  123. def get_results(queryset, fields, size):
  124. &lt;span class="hljs-deletion"&gt;-   count = queryset.count()&lt;/span&gt;
  125.    results = []
  126.    for record in queryset.values(*fields)[:size]:
  127.        results.append(record)
  128. &lt;span class="hljs-addition"&gt;+   count = len(results)&lt;/span&gt;
  129.    return {&amp;quot;count&amp;quot;: count, &amp;quot;results&amp;quot;: results}
  130. &lt;/code&gt;&lt;/pre&gt;
  131.  
  132. &lt;p&gt;But that is wrong. The &lt;code&gt;count&lt;/code&gt; would max out at whatever the &lt;code&gt;size&lt;/code&gt; is.&lt;/p&gt;
  133. &lt;p&gt;The &lt;strong&gt;solution&lt;/strong&gt; is to try to avoid the potentially unnecessary &lt;code&gt;.count()&lt;/code&gt; query.&lt;/p&gt;
  134. &lt;pre&gt;&lt;code class="hljs"&gt;
  135. &lt;span class="hljs-keyword"&gt;def&lt;/span&gt; &lt;span class="hljs-title function_"&gt;get_results&lt;/span&gt;(&lt;span class="hljs-params"&gt;queryset, fields, size&lt;/span&gt;):
  136.    count = &lt;span class="hljs-number"&gt;0&lt;/span&gt;
  137.    results = []
  138.    &lt;span class="hljs-keyword"&gt;for&lt;/span&gt; i, record &lt;span class="hljs-keyword"&gt;in&lt;/span&gt; &lt;span class="hljs-built_in"&gt;enumerate&lt;/span&gt;(queryset.values(*fields)[: size + &lt;span class="hljs-number"&gt;1&lt;/span&gt;]):
  139.        &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; i == size:
  140.            &lt;span class="hljs-comment"&gt;# Alas, there are more records than the pagination&lt;/span&gt;
  141.            count = queryset.count()
  142.            &lt;span class="hljs-keyword"&gt;break&lt;/span&gt;
  143.        count = i + &lt;span class="hljs-number"&gt;1&lt;/span&gt;
  144.        results.append(record)
  145.    &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; {&lt;span class="hljs-string"&gt;&amp;quot;count&amp;quot;&lt;/span&gt;: count, &lt;span class="hljs-string"&gt;&amp;quot;results&amp;quot;&lt;/span&gt;: results}
  146. &lt;/code&gt;&lt;/pre&gt;
  147.  
  148. &lt;p&gt;This way, you only incur &lt;em&gt;one&lt;/em&gt; database query when there wasn't that much to find, but if there was more than what the pagination called for, you have to incur that extra database query.&lt;/p&gt;</description><pubDate>Wed, 14 Feb 2024 03:09:07 +0000</pubDate><guid>https://www.peterbe.com/plog/how-to-avoid-a-count-query-in-django-if-you-can</guid></item><item><title>How to restore all unstaged files in with git</title><link>https://www.peterbe.com/plog/restore-all-unstaged-files-git</link><description>`git restore -- .`</description><pubDate>Thu, 08 Feb 2024 17:53:27 +0000</pubDate><guid>https://www.peterbe.com/plog/restore-all-unstaged-files-git</guid></item><item><title>How slow is Node to Brotli decompress a file compared to not having to decompress?</title><link>https://www.peterbe.com/plog/how-slow-is-node-to-brotli-decompress</link><description>tl;dr; Not very slow.</description><pubDate>Fri, 19 Jan 2024 14:01:47 +0000</pubDate><guid>https://www.peterbe.com/plog/how-slow-is-node-to-brotli-decompress</guid></item><item><title>Search hidden directories with ripgrep, by default</title><link>https://www.peterbe.com/plog/hidden-directories-rg-by-default</link><description>I encourage you to set `alias rg='rg --hidden'` to make ripgrep always find hidden files</description><pubDate>Sat, 30 Dec 2023 17:40:23 +0000</pubDate><guid>https://www.peterbe.com/plog/hidden-directories-rg-by-default</guid></item><item><title>fnm is much faster than nvm.</title><link>https://www.peterbe.com/plog/fnm-faster-than-nvm</link><description>`fnm` is much faster than `nvm` and the transition is easy</description><pubDate>Thu, 28 Dec 2023 01:05:48 +0000</pubDate><guid>https://www.peterbe.com/plog/fnm-faster-than-nvm</guid></item><item><title>Comparing different efforts with WebP in Sharp</title><link>https://www.peterbe.com/plog/comparing-different-efforts-with-webp-in-sharp</link><description>&lt;p&gt;When you, in a Node program, use &lt;a href="https://sharp.pixelplumbing.com/api-output#webp"&gt;&lt;code&gt;sharp&lt;/code&gt;&lt;/a&gt; to convert an image buffer to a WebP buffer, you have an option of &lt;strong&gt;effort&lt;/strong&gt;. The higher the number the longer it takes but the image it produces is smaller on disk.&lt;/p&gt;
  149. &lt;p&gt;I wanted to put some realistic numbers for this, so I wrote a benchmark, run on my Intel MacbookPro.&lt;/p&gt;
  150. &lt;h3&gt;The benchmark&lt;/h3&gt;
  151. &lt;p&gt;It looks like this:&lt;/p&gt;
  152. &lt;pre&gt;&lt;code class="hljs"&gt;
  153. &lt;span class="hljs-keyword"&gt;async&lt;/span&gt; &lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title function_"&gt;e6&lt;/span&gt;(&lt;span class="hljs-params"&gt;&lt;/span&gt;) {
  154.  &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; &lt;span class="hljs-title function_"&gt;f&lt;/span&gt;(&lt;span class="hljs-string"&gt;&amp;quot;screenshot-1000.png&amp;quot;&lt;/span&gt;, &lt;span class="hljs-number"&gt;6&lt;/span&gt;);
  155. }
  156. &lt;span class="hljs-keyword"&gt;async&lt;/span&gt; &lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title function_"&gt;e5&lt;/span&gt;(&lt;span class="hljs-params"&gt;&lt;/span&gt;) {
  157.  &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; &lt;span class="hljs-title function_"&gt;f&lt;/span&gt;(&lt;span class="hljs-string"&gt;&amp;quot;screenshot-1000.png&amp;quot;&lt;/span&gt;, &lt;span class="hljs-number"&gt;5&lt;/span&gt;);
  158. }
  159. &lt;span class="hljs-keyword"&gt;async&lt;/span&gt; &lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title function_"&gt;e4&lt;/span&gt;(&lt;span class="hljs-params"&gt;&lt;/span&gt;) {
  160.  &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; &lt;span class="hljs-title function_"&gt;f&lt;/span&gt;(&lt;span class="hljs-string"&gt;&amp;quot;screenshot-1000.png&amp;quot;&lt;/span&gt;, &lt;span class="hljs-number"&gt;4&lt;/span&gt;);
  161. }
  162. &lt;span class="hljs-keyword"&gt;async&lt;/span&gt; &lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title function_"&gt;e3&lt;/span&gt;(&lt;span class="hljs-params"&gt;&lt;/span&gt;) {
  163.  &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; &lt;span class="hljs-title function_"&gt;f&lt;/span&gt;(&lt;span class="hljs-string"&gt;&amp;quot;screenshot-1000.png&amp;quot;&lt;/span&gt;, &lt;span class="hljs-number"&gt;3&lt;/span&gt;);
  164. }
  165. &lt;span class="hljs-keyword"&gt;async&lt;/span&gt; &lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title function_"&gt;e2&lt;/span&gt;(&lt;span class="hljs-params"&gt;&lt;/span&gt;) {
  166.  &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; &lt;span class="hljs-title function_"&gt;f&lt;/span&gt;(&lt;span class="hljs-string"&gt;&amp;quot;screenshot-1000.png&amp;quot;&lt;/span&gt;, &lt;span class="hljs-number"&gt;2&lt;/span&gt;);
  167. }
  168. &lt;span class="hljs-keyword"&gt;async&lt;/span&gt; &lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title function_"&gt;e1&lt;/span&gt;(&lt;span class="hljs-params"&gt;&lt;/span&gt;) {
  169.  &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; &lt;span class="hljs-title function_"&gt;f&lt;/span&gt;(&lt;span class="hljs-string"&gt;&amp;quot;screenshot-1000.png&amp;quot;&lt;/span&gt;, &lt;span class="hljs-number"&gt;1&lt;/span&gt;);
  170. }
  171. &lt;span class="hljs-keyword"&gt;async&lt;/span&gt; &lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title function_"&gt;e0&lt;/span&gt;(&lt;span class="hljs-params"&gt;&lt;/span&gt;) {
  172.  &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; &lt;span class="hljs-title function_"&gt;f&lt;/span&gt;(&lt;span class="hljs-string"&gt;&amp;quot;screenshot-1000.png&amp;quot;&lt;/span&gt;, &lt;span class="hljs-number"&gt;0&lt;/span&gt;);
  173. }
  174.  
  175. &lt;span class="hljs-keyword"&gt;async&lt;/span&gt; &lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title function_"&gt;f&lt;/span&gt;(&lt;span class="hljs-params"&gt;fp, effort&lt;/span&gt;) {
  176.  &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; originalBuffer = &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; fs.&lt;span class="hljs-title function_"&gt;readFile&lt;/span&gt;(fp);
  177.  &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; image = &lt;span class="hljs-title function_"&gt;sharp&lt;/span&gt;(originalBuffer);
  178.  &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; { width } = &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; image.&lt;span class="hljs-title function_"&gt;metadata&lt;/span&gt;();
  179.  &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; buffer = &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; image.&lt;span class="hljs-title function_"&gt;webp&lt;/span&gt;({ effort }).&lt;span class="hljs-title function_"&gt;toBuffer&lt;/span&gt;();
  180.  &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; [buffer.&lt;span class="hljs-property"&gt;length&lt;/span&gt;, width, { effort }];
  181. }
  182. &lt;/code&gt;&lt;/pre&gt;
  183.  
  184. &lt;p&gt;Then, I ran each function in serial and measured how long it took. Then, do that whole thing 15 times. So, in total, each function is executed 15 times. The numbers are collected and the &lt;strong&gt;median&lt;/strong&gt; (P50) is reported.&lt;/p&gt;
  185. &lt;h3&gt;A 2000x2000 pixel PNG image&lt;/h3&gt;
  186. &lt;pre&gt;1. e0: 191ms                   235KB
  187. 2. e1: 340.5ms                 208KB
  188. 3. e2: 369ms                   198KB
  189. 4. e3: 485.5ms                 193KB
  190. 5. e4: 587ms                   177KB
  191. 6. e5: 695.5ms                 177KB
  192. 7. e6: 4811.5ms                142KB&lt;/pre&gt;
  193.  
  194. &lt;p&gt;What it means is that if you use &lt;code&gt;{effort: 6}&lt;/code&gt; the conversion of a 2000x2000 PNG took 4.8 seconds but the resulting WebP buffer became 142KB instead of the least effort which made it 235 KB.&lt;/p&gt;
  195. &lt;!-- &lt;a href="/cache/6a/6b/6a6bfe311cc20f1564d51ce3177ccd73.png"&gt;&lt;img src="/cache/98/50/985031950d0e1ff7366a43d949b4f86f.png" alt="Comparing effort, time and size" width="370" height="229"&gt;&lt;/a&gt; --&gt;
  196.  
  197. &lt;p&gt;&lt;a href="/cache/6a/6b/6a6bfe311cc20f1564d51ce3177ccd73.png"&gt;&lt;img src="/cache/6a/6b/6a6bfe311cc20f1564d51ce3177ccd73.png" alt="Comparing effort, time and size" &gt;&lt;/a&gt;&lt;/p&gt;
  198. &lt;p&gt;This graph demonstrates how the (blue) time goes up the more effort you put in. And how the final size (red) goes down the more effort you put in.&lt;/p&gt;
  199. &lt;h3&gt;A 1000x1000 pixel PNG image&lt;/h3&gt;
  200. &lt;pre&gt;1. e0: 54ms                    70KB
  201. 2. e1: 60ms                    66KB
  202. 3. e2: 65ms                    61KB
  203. 4. e3: 96ms                    59KB
  204. 5. e4: 169ms                   53KB
  205. 6. e5: 193ms                   53KB
  206. 7. e6: 1466ms                  51KB&lt;/pre&gt;
  207.  
  208. &lt;h3&gt;A 500x500 pixel PNG image&lt;/h3&gt;
  209. &lt;pre&gt;1. e0: 24ms                    23KB
  210. 2. e1: 26ms                    21KB
  211. 3. e2: 28ms                    20KB
  212. 4. e3: 37ms                    19KB
  213. 5. e4: 57ms                    18KB
  214. 6. e5: 66ms                    18KB
  215. 7. e6: 556ms                   18KB&lt;/pre&gt;
  216.  
  217. &lt;h2&gt;Conclusion&lt;/h2&gt;
  218. &lt;p&gt;Up to you but clearly, &lt;code&gt;{effort: 6}&lt;/code&gt; is to be avoided if you're worried about it taking a &lt;em&gt;huge&lt;/em&gt; amount of time to make the conversion.&lt;/p&gt;
  219. &lt;p&gt;Perhaps the takeaway is; that if you run these operations in the build step such that you don't have to ever do it again, it's worth the maximum effort. Beyond that, find a sweet spot for your particular environment and challenge.&lt;/p&gt;</description><pubDate>Thu, 05 Oct 2023 13:51:07 +0000</pubDate><guid>https://www.peterbe.com/plog/comparing-different-efforts-with-webp-in-sharp</guid></item><item><title>Zipping files is appending by default - Watch out!</title><link>https://www.peterbe.com/plog/zipping-files-is-appending-by-default</link><description>`zip -r my.zip some-directory` will, by default, append the stuff in `some-directory`</description><pubDate>Wed, 04 Oct 2023 17:14:15 +0000</pubDate><guid>https://www.peterbe.com/plog/zipping-files-is-appending-by-default</guid></item></channel></rss>

If you would like to create a banner that links to this page (i.e. this validation result), do the following:

  1. Download the "valid RSS" banner.

  2. Upload the image to your own server. (This step is important. Please do not link directly to the image on this server.)

  3. Add this HTML to your page (change the image src attribute if necessary):

If you would like to create a text link instead, here is the URL you can use:

http://www.feedvalidator.org/check.cgi?url=https%3A//www.peterbe.com/rss.xml

Copyright © 2002-9 Sam Ruby, Mark Pilgrim, Joseph Walton, and Phil Ringnalda