<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>arek o sofcie</title>
    <link>https://baal.ar76.eu/arek/</link>
    <description>Różności na temat tworzenia oprogramowania</description>
    <pubDate>Mon, 04 May 2026 16:40:03 +0200</pubDate>
    <item>
      <title>Copy Fail</title>
      <link>https://baal.ar76.eu/arek/copy-fail</link>
      <description>&lt;![CDATA[Całkiem fajna ta podatność w Linuksie:&#xA;&#xA;https://copy.fail/&#xA;&#xA;Z przykrych rzeczy - spodziewałem się, że Debian będzie miał jakiegoś patcha do kernela. A tu nic. Trzeba sobie zablokować podatny moduł:&#xA;&#xA;echo &#34;install algifaead /bin/false&#34;   /etc/modprobe.d/disable-algif.conf&#xA;Za to Ubuntu dostarczył aktualizację.&#xA;&#xA;Swoją drogą - ciekawe, czy to odkrycie w kernelu to efekt AI.&#xA;&#xA;  EDIT&#xA;  Zwracam honor Debianowi - kilka godzin później poprawka do kernela się pojawiła.&#xA;&#xA;@arkr]]&gt;</description>
      <content:encoded><![CDATA[<p>Całkiem fajna ta podatność w Linuksie:</p>

<p><a href="https://copy.fail/" rel="nofollow">https://copy.fail/</a></p>

<p>Z przykrych rzeczy – spodziewałem się, że Debian będzie miał jakiegoś patcha do kernela. A tu nic. Trzeba sobie zablokować podatny moduł:</p>

<pre><code class="language-bash">echo &#34;install algif_aead /bin/false&#34; &gt; /etc/modprobe.d/disable-algif.conf
</code></pre>

<p>Za to Ubuntu dostarczył aktualizację.</p>

<p>Swoją drogą – ciekawe, czy to odkrycie w kernelu to efekt AI.</p>

<blockquote><p>EDIT
Zwracam honor Debianowi – kilka godzin później poprawka do kernela się pojawiła.</p></blockquote>

<p><a href="https://mastodon.social/@ark_r" rel="nofollow">@ark_r</a></p>
]]></content:encoded>
      <guid>https://baal.ar76.eu/arek/copy-fail</guid>
      <pubDate>Thu, 30 Apr 2026 20:43:34 +0000</pubDate>
    </item>
    <item>
      <title>Intel Arc Pro B50 - zmiany</title>
      <link>https://baal.ar76.eu/arek/intel-arc-pro-b50-zmiany</link>
      <description>&lt;![CDATA[Ciekawa niespodzianka po aktualizacji kernela z 6.14 na 6.17.&#xA;&#xA;Mój pecet nie ma obsługi ReBAR, co znaczy, że CPU miał okno komunikacji z VRAM ograniczone do 256MB. &#xA;&#xA;Okazuje się jednak, że kernel 6.17 potrafi zignorować braki informacji z UEFI  i samodzielnie odgadnąć potrzebę przemapowania zasobów na PCI.&#xA;dmesg pokazuje serię powtarzających się komunikatów o alokacji zasobów PCI poprzetykana wpisami typu&#xA;&#xA;[    0.356844] PCI: No. 4 try to assign unassigned res&#xA;&#xA;I czwarta próba zakończyła się powodzeniem.&#xA;&#xA;LM Studio bez problemu obsłużyło model gtp-oss:20b.&#xA;&#xA;Dodatkowo libva zaczęło funkcjonować poprawnie i w końcu mam sprzętowe dekodowanie video av1.&#xA;&#xA;Jak się okazuje, Linux naprawdę pozwala przywrócić do użycia stare pecety.&#xA;&#xA;linux &#xA;&#xA;@arkr]]&gt;</description>
      <content:encoded><![CDATA[<p>Ciekawa niespodzianka po aktualizacji kernela z 6.14 na 6.17.</p>

<p>Mój pecet nie ma obsługi ReBAR, co znaczy, że CPU miał okno komunikacji z VRAM ograniczone do 256MB.</p>

<p>Okazuje się jednak, że kernel 6.17 potrafi zignorować braki informacji z UEFI  i samodzielnie odgadnąć potrzebę przemapowania zasobów na PCI.
<code>dmesg</code> pokazuje serię powtarzających się komunikatów o alokacji zasobów PCI poprzetykana wpisami typu</p>

<pre><code>[    0.356844] PCI: No. 4 try to assign unassigned res
</code></pre>

<p>I czwarta próba zakończyła się powodzeniem.</p>

<p>LM Studio bez problemu obsłużyło model gtp-oss:20b.</p>

<p>Dodatkowo libva zaczęło funkcjonować poprawnie i w końcu mam sprzętowe dekodowanie video av1.</p>

<p>Jak się okazuje, Linux naprawdę pozwala przywrócić do użycia stare pecety.</p>

<p><a href="/arek/tag:linux" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">linux</span></a></p>

<p><a href="https://mastodon.social/@ark_r" rel="nofollow">@ark_r</a></p>
]]></content:encoded>
      <guid>https://baal.ar76.eu/arek/intel-arc-pro-b50-zmiany</guid>
      <pubDate>Sun, 08 Feb 2026 13:14:10 +0000</pubDate>
    </item>
    <item>
      <title>Intel Arc Pro B50 na Ubuntu 24.04.3</title>
      <link>https://baal.ar76.eu/arek/intel-arc-pro-b50-na-ubuntu-24-04-3</link>
      <description>&lt;![CDATA[Kupiłem sobie kartę Arc Pro B50, żeby zastąpić starą kartę 1070 NVidii. Na razie chyba trzeba powiedzieć sobie, że w przypadku Linuxa jest trochę za wcześnie na ten sprzęt.&#xA;&#xA;Problemy&#xA;&#xA;Kartę wziąłem, bo ma 16GB VRAM, była tania i pobiera tylko 70W. Wydawało się OK na mój stary Dell Inc. XPS 8930.&#xA;&#xA;Ale:&#xA;Przydałaby się obsługa ReBAR, żeby CPU miał większe okno dostępu pamięci karty - mam tylko 256MB, a mogłoby być 16GB,&#xA;Wygląda na to, że zderzenie PCIe 3.0 płyty z kartą PCIe 5.0 skończyło się tym, że komunikacja jest na jednej linii - LnkSta:&#x9;Speed 2.5GT/s, Width x1. Trochę słabo.&#xA;&#xA;Kolejny problem: libva nie działa. Ściągnąłem najnowsze biblioteki Intela z PPA, ale nawet uruchomienie vainfo kończy się crash-em. Żegnaj akceleracjo sprzętowa :)&#xA;&#xA;Ciekawa sytuacja z chrome pod wayland: gdy Gnome wygasi ekran, desktop wywala się podczas ponownego włączania ekranu.&#xA;&#xA;W przeglądarce widać pewne artefakty graficzne przy niektórych animacjach.&#xA;&#xA;Wygląda na to, że sterownik xe w wersji 6.14.0-37-generic kernela nie jest jeszcze dopracowany.&#xA;&#xA;Pozytywy&#xA;&#xA;Akceleracja 3D (OpenGL, Vulkan, WebGL, WebGPU) działa. Wydajność jest całkiem przyjemna.&#xA;&#xA;Dało się odpalić ollama w trybie OLLAMAVULKAN=1. Wydajność nie jest powalająca, mimo że model w całości jest obsługiwany przez GPU. Może będzie się to poprawiać.&#xA;&#xA;Pewnie i tak będę zmieniał komputer (jak ceny RAMu spadną :) ) i zobaczymy co będzie dalej.&#xA;&#xA;Na Windows karta działa całkiem OK, mimo starej płyty głównej. Nawet Cyberpunka z Light Tracing odpaliłem. &#xA;&#xA;Nie lubię zakupów sprzętu.&#xA;&#xA;@arkr]]&gt;</description>
      <content:encoded><![CDATA[<p>Kupiłem sobie kartę Arc Pro B50, żeby zastąpić starą kartę 1070 NVidii. Na razie chyba trzeba powiedzieć sobie, że w przypadku Linuxa jest trochę za wcześnie na ten sprzęt.</p>

<h2 id="problemy">Problemy</h2>

<p>Kartę wziąłem, bo ma 16GB VRAM, była tania i pobiera tylko 70W. Wydawało się OK na mój stary Dell Inc. XPS 8930.</p>

<p>Ale:
* Przydałaby się obsługa ReBAR, żeby CPU miał większe okno dostępu pamięci karty – mam tylko 256MB, a mogłoby być 16GB,
* Wygląda na to, że zderzenie PCIe 3.0 płyty z kartą PCIe 5.0 skończyło się tym, że komunikacja jest na jednej linii – <code>LnkSta: Speed 2.5GT/s, Width x1</code>. Trochę słabo.</p>

<p>Kolejny problem: libva nie działa. Ściągnąłem najnowsze biblioteki Intela z PPA, ale nawet uruchomienie <code>vainfo</code> kończy się crash-em. Żegnaj akceleracjo sprzętowa :)</p>

<p>Ciekawa sytuacja z chrome pod wayland: gdy Gnome wygasi ekran, desktop wywala się podczas ponownego włączania ekranu.</p>

<p>W przeglądarce widać pewne artefakty graficzne przy niektórych animacjach.</p>

<p>Wygląda na to, że sterownik <code>xe</code> w wersji 6.14.0-37-generic kernela nie jest jeszcze dopracowany.</p>

<h2 id="pozytywy">Pozytywy</h2>

<p>Akceleracja 3D (OpenGL, Vulkan, WebGL, WebGPU) działa. Wydajność jest całkiem przyjemna.</p>

<p>Dało się odpalić ollama w trybie OLLAMA_VULKAN=1. Wydajność nie jest powalająca, mimo że model w całości jest obsługiwany przez GPU. Może będzie się to poprawiać.</p>

<p>Pewnie i tak będę zmieniał komputer (jak ceny RAMu spadną :) ) i zobaczymy co będzie dalej.</p>

<p>Na Windows karta działa całkiem OK, mimo starej płyty głównej. Nawet Cyberpunka z Light Tracing odpaliłem.</p>

<p>Nie lubię zakupów sprzętu.</p>

<p><a href="https://mastodon.social/@ark_r" rel="nofollow">@ark_r</a></p>
]]></content:encoded>
      <guid>https://baal.ar76.eu/arek/intel-arc-pro-b50-na-ubuntu-24-04-3</guid>
      <pubDate>Sun, 14 Dec 2025 10:47:46 +0000</pubDate>
    </item>
    <item>
      <title>Jeszcze raz w temacie fediverse:creator</title>
      <link>https://baal.ar76.eu/arek/jeszcze-raz-w-temacie-fediverse-creator</link>
      <description>&lt;![CDATA[W teorii zrobiłem już wszystko i tag powinien działać.&#xA;&#xA;Może to trochę spam, ale inaczej tego nie sprawdzę.&#xA;&#xA;@arkr]]&gt;</description>
      <content:encoded><![CDATA[<p>W teorii zrobiłem już wszystko i tag powinien działać.</p>

<p>Może to trochę spam, ale inaczej tego nie sprawdzę.</p>

<p><a href="https://mastodon.social/@ark_r" rel="nofollow">@ark_r</a></p>
]]></content:encoded>
      <guid>https://baal.ar76.eu/arek/jeszcze-raz-w-temacie-fediverse-creator</guid>
      <pubDate>Sat, 13 Dec 2025 22:56:01 +0000</pubDate>
    </item>
    <item>
      <title>Aktualizacja writefreely do wersji 0.16.0</title>
      <link>https://baal.ar76.eu/arek/aktualizacja-writefreely-do-wersji-0-16-0</link>
      <description>&lt;![CDATA[Tak, pewna niespodzianka, ale blog wiąż żyje.&#xA;Chociaż nie mam czasu, żeby coś pisać.&#xA;&#xA;Ale zrobiłem aktualizację i piszę posta, żeby sprawdzić, czy faktycznie nowe funkcje zadziałają.&#xA;&#xA;Na pewno działają &#34;lajki&#34; - tj. gdy ktoś da fava np. na mastodonie, to widać to w artykule.&#xA;&#xA;A teraz jeszcze kwestia taga fediverse:creator.&#xA;&#xA;Ale to muszę dopiero ogarnąć. Może zadziała, a może nie.&#xA;&#xA;Edit... no jakoś &#34;creatora&#34; nie ma.&#xA;&#xA;writefreely&#xA;&#xA;@arkr]]&gt;</description>
      <content:encoded><![CDATA[<p>Tak, pewna niespodzianka, ale blog wiąż żyje.
Chociaż nie mam czasu, żeby coś pisać.</p>

<p>Ale zrobiłem aktualizację i piszę posta, żeby sprawdzić, czy faktycznie nowe funkcje zadziałają.</p>

<p>Na pewno działają “lajki” – tj. gdy ktoś da fava np. na mastodonie, to widać to w artykule.</p>

<p>A teraz jeszcze kwestia taga <code>fediverse:creator</code>.</p>

<p>Ale to muszę dopiero ogarnąć. Może zadziała, a może nie.</p>

<p><em>Edit... no jakoś “creatora” nie ma.</em></p>

<p><a href="/arek/tag:writefreely" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">writefreely</span></a></p>

<p><a href="https://mastodon.social/@ark_r" rel="nofollow">@ark_r</a></p>
]]></content:encoded>
      <guid>https://baal.ar76.eu/arek/aktualizacja-writefreely-do-wersji-0-16-0</guid>
      <pubDate>Sat, 13 Dec 2025 18:46:42 +0000</pubDate>
    </item>
    <item>
      <title>Power-off problem</title>
      <link>https://baal.ar76.eu/arek/power-off-problem</link>
      <description>&lt;![CDATA[Przy którejś aktualizacji kernela (gdzieś między 5.19, a 6.1) mój pecet nabył ciekawego problemu: wyłączanie kończyło się jak lata temu na windows, gdzie użytkownika żegnał napis &#34;Teraz możesz wyłączyć komputer&#34;.&#xA;&#xA;Cała procedura shutdown-u przechodziła, ale na samym końcu, gdy kernel wywołuje acpipoweroff, proces się zawieszał i musiałem wyłączać zasilanie przytrzymując kilka sekund przycisk na obudowie.&#xA;&#xA;Przy okazji - usypianie (sleep/suspend) również przestało działać, ale tego akurat nie używałem zbyt często, więc przeszkadzało mi znacznie mniej.&#xA;&#xA;Historia poszukiwania rozwiązania jest nudna jak flaki z olejem (ale sporo ciekawych rzeczy o ACPI można się dowiedzieć), więc od razu przejdę do rozwiązania (a przynajmniej czegoś, co u mnie to zadziałało).&#xA;&#xA;Zgodnie z https://www.kernel.org/doc/Documentation/Intel-IOMMU.txt mój oparty na CPU Intela pecet może obsługiwać IOMMU dzięki stosownemu modułowi i zdaje się, że właśnie w kernelu 6.x dostarczanemu z Ubuntu ta funkcjonalność została włączona.&#xA;&#xA;Pecet ma zintegrowaną grafikę Intela (której nie używam) i dodatkowo kartę NVIDIA. I chyba coś z tą nieużywaną grafiką Intela szwankowało.&#xA;&#xA;We wskazanym dokumencie można przeczytać:&#xA;&#xA;Graphics Problems?&#xA;------------------&#xA;If you encounter issues with graphics devices, you can try adding&#xA;option inteliommu=igfxoff to turn off the integrated graphics engine.&#xA;If this fixes anything, please ensure you file a bug reporting the problem.&#xA;&#xA;I właśnie owo inteliommu=igfxoff rozwiązuje problem. Wartość off również, ale podobno iommu poprawia bezpieczeństwo (i przydaje się, gdy się uruchamia maszyny wirtualne).&#xA;&#xA;W każdym razie - zadziałało. Po dodaniu do parametrów kernela w grub problem ustąpił i shutdown -P wyłącza zasilanie. &#xA;&#xA;#linux #poweroff #iommu&#xA;&#xA;@arkr]]&gt;</description>
      <content:encoded><![CDATA[<p>Przy którejś aktualizacji kernela (gdzieś między 5.19, a 6.1) mój pecet nabył ciekawego problemu: wyłączanie kończyło się jak lata temu na windows, gdzie użytkownika żegnał napis “Teraz możesz wyłączyć komputer”.</p>

<p>Cała procedura shutdown-u przechodziła, ale na samym końcu, gdy kernel wywołuje acpi_poweroff, proces się zawieszał i musiałem wyłączać zasilanie przytrzymując kilka sekund przycisk na obudowie.</p>

<p>Przy okazji – usypianie (sleep/suspend) również przestało działać, ale tego akurat nie używałem zbyt często, więc przeszkadzało mi znacznie mniej.</p>

<p>Historia poszukiwania rozwiązania jest nudna jak flaki z olejem (ale sporo ciekawych rzeczy o ACPI można się dowiedzieć), więc od razu przejdę do rozwiązania (a przynajmniej czegoś, co u mnie to zadziałało).</p>

<p>Zgodnie z <a href="https://www.kernel.org/doc/Documentation/Intel-IOMMU.txt" rel="nofollow">https://www.kernel.org/doc/Documentation/Intel-IOMMU.txt</a> mój oparty na CPU Intela pecet może obsługiwać IOMMU dzięki stosownemu modułowi i zdaje się, że właśnie w kernelu 6.x dostarczanemu z Ubuntu ta funkcjonalność została włączona.</p>

<p>Pecet ma zintegrowaną grafikę Intela (której nie używam) i dodatkowo kartę NVIDIA. I chyba coś z tą nieużywaną grafiką Intela szwankowało.</p>

<p>We wskazanym dokumencie można przeczytać:</p>

<pre><code>Graphics Problems?
------------------
If you encounter issues with graphics devices, you can try adding
option intel_iommu=igfx_off to turn off the integrated graphics engine.
If this fixes anything, please ensure you file a bug reporting the problem.
</code></pre>

<p>I właśnie owo <code>intel_iommu=igfx_off</code> rozwiązuje problem. Wartość <code>off</code> również, ale podobno iommu poprawia bezpieczeństwo (i przydaje się, gdy się uruchamia maszyny wirtualne).</p>

<p>W każdym razie – zadziałało. Po dodaniu do parametrów kernela w grub problem ustąpił i <code>shutdown -P</code> wyłącza zasilanie.</p>

<p><a href="/arek/tag:linux" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">linux</span></a> <a href="/arek/tag:poweroff" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">poweroff</span></a> <a href="/arek/tag:iommu" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">iommu</span></a></p>

<p><a href="https://mastodon.social/@ark_r" rel="nofollow">@ark_r</a></p>
]]></content:encoded>
      <guid>https://baal.ar76.eu/arek/power-off-problem</guid>
      <pubDate>Sat, 02 Nov 2024 20:52:49 +0000</pubDate>
    </item>
    <item>
      <title>Aktualizacja writefreely do wersji 0.14.0</title>
      <link>https://baal.ar76.eu/arek/aktualizacja-writefreely-do-wersji-0-14-0</link>
      <description>&lt;![CDATA[Blog działa na komputerku z procesorem w architekturze ARM64, a writefreely ostatnio nie publikował buildów dla architektury innej niż ADM64.&#xA;&#xA;Na szczęście ich problemy się rozwiązały i mogłem zrobić aktualizację.&#xA;&#xA;Z większych zmian... z tego co widzę, to można już bez sztuczek obsłużyć weryfikację identyfikatora mastodon (rel=&#34;me), co niniejszym uczyniłem.&#xA;&#xA;Aktualizacja sprowadza się do rozpakowania archiwum i wykonania&#xA;&#xA;./writefreely db migrate&#xA;&#xA;Do ogarnięcia :)&#xA;&#xA;@arkr]]&gt;</description>
      <content:encoded><![CDATA[<p>Blog działa na komputerku z procesorem w architekturze ARM64, a writefreely ostatnio nie publikował buildów dla architektury innej niż ADM64.</p>

<p>Na szczęście ich problemy się rozwiązały i mogłem zrobić aktualizację.</p>

<p>Z większych zmian... z tego co widzę, to można już bez sztuczek obsłużyć weryfikację identyfikatora mastodon (<code>rel=&#34;me</code>), co niniejszym uczyniłem.</p>

<p>Aktualizacja sprowadza się do rozpakowania archiwum i wykonania</p>

<pre><code class="language-sh">./writefreely db migrate
</code></pre>

<p>Do ogarnięcia :)</p>

<p><a href="https://mastodon.social/@ark_r" rel="nofollow">@ark_r</a></p>
]]></content:encoded>
      <guid>https://baal.ar76.eu/arek/aktualizacja-writefreely-do-wersji-0-14-0</guid>
      <pubDate>Sat, 23 Sep 2023 18:12:47 +0000</pubDate>
    </item>
    <item>
      <title>Linux disk quota</title>
      <link>https://baal.ar76.eu/arek/linux-disk-quota</link>
      <description>&lt;![CDATA[Z jakiś powodów zapragnąłem ustawić w Linuksie limity na użycie dysku. Pominę opis poszukiwania informacji w Internecie, ale w skrócie - z nieznanych mi powodów w zdecydowanej przewadze opisywane są jakieś starocie. W artykule pokrótce jak to wygląda w aktualnych wersjach jądra. !--more--&#xA;&#xA;Kiedyś&#xA;&#xA;Kiedyś było tak: poza filesystemem XFS, informacje o użyciu dysku i inodach był zapisywany w aplikach quota.user i quota.group. Potem coś poprawiono i nowe pliki nazywają się aquota.user i aquota.group.&#xA;&#xA;Żeby limitowanie działało, dysk należy zamontować z opcjami usrquota i grpquota. Kolejna nowość wprowadziła usprawnienie dotyczące sposobu rejestrowania aktualnego użycia dysku dla użytkownika. Opcje montowania zmieniły się na usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv1 i dzięki temu pliki ze zużyciem dysku aktualizowane razem ze zmianami w plikach i przechodzą przez journal.&#xA;&#xA;Potem trzeba uruchomić&#xA;sudo quotacheck -cugv /mnt/   &#xA;i po&#xA;sudo quotaon -ug /mnt&#xA;możliwość limitowania dysku jest włączona.&#xA;&#xA;Ale quotacheck wypisało mi komunikat:&#xA;quotacheck: Your kernel probably supports ext4 quota feature but you are using external quota files. Please switch your filesystem to use ext4 quota feature as external quota files on ext4 are deprecated.&#xA;I tak dowiedziałem się, że ext4 wspiera pliki limitów jako ukryte inody, tylko trzeba tę opcję włączyć.&#xA;&#xA;A tak to wygląda teraz&#xA;&#xA;Zatem, jak to wygląda w na nowym jądrze. &#xA;&#xA;Założenia: mam dysk... np. /dev/loop30, montuję go na /mnt.&#xA;&#xA;Kernel został zbudowany z opcjami&#xA;CONFIGQUOTA=y&#xA;CONFIGQUOTATREE=m&#xA;&#xA;Mamy narzędzia do obsługi limitów (np. apt install quota na Ubuntu)&#xA;&#xA;Mamy niezbędne moduły kernela (sudo modprobe quotav2 działa).&#xA;&#xA;Procedura jest następująca:&#xA;&#xA;Tworzę filesystem (ext4):&#xA;sudo mkfs.ext4 /dev/loop30&#xA;Włączam wewnętrzną obsługę limitów (to jest ta najnowsza nowość)&#xA;&#xA;sudo tune2fs -O quota /dev/loop30&#xA;Feature quota oznacza: &#34;Enable internal file system quota inodes&#34;&#xA;&#xA;A teraz jeszcze jedno: można mieć nie tylko limity per user i per grupa ale również limit per projekt. Nie mam pojęcia jak działa to ostatnie, ale można je wszystkie niezależnie włączać i wyłączać (patrz man tune2fs):&#xA;sudo tune2fs -Q usrquota,grpquota,prjquota /dev/loop30&#xA;Można sprawdzić, że limity zostały włączone wywołując:&#xA;sudo tune2fs -l /dev/loop30 | grep &#34;Filesystem features&#34;&#xA;&#xA;Filesystem features:      hasjournal extattr resizeinode dirindex filetype extent 64bit flexbg sparsesuper largefile hugefile dirnlink extraisize quota metadatacsum project&#xA;Obecność słowa &#34;quota&#34; oznacza, że feature jest włączony.&#xA;&#xA;Kolejny krok: montowanie filesystemu:&#xA;sudo mount -o usrquota,grpquota,prjquota /dev/loop30 /mnt&#xA;Pewnie dobrze jest sobie ten mountpoint wpisać do /etc/fstab, ale chodzi po prostu o to, żeby włączyć dla tego dysku odpowiednie zliczanie limitów.&#xA;&#xA;Żadnych plików limitów w /mnt nie widać, ale możemy uruchomić&#xA;sudo repquota /mnt&#xA;I zobaczymy zużycie  tego dysku przez użytkownika root (bo tylko jego obiekty są na razie w tym katalogu).&#xA;Przy okazji, repquota z parametrem -g pokaże limity dla grup, a -P limity dla projektów (jak ustalę jak zrobić taki projekt, to na pewno to opiszę).&#xA;&#xA;Limity można ustawić poleceniami setquota i edquota (ale to już standard - każdy manual opisuje jak to zrobić).&#xA;&#xA;Wygląda na to, że jeśli użyliśmy podczas montowania opcji usrquota, grpquta etc, to limity są od razu egzekwowane (bez konieczności włączania ich za pomocą quotaon), co można sprawdzić używając opcji -p:&#xA;sudo quotaon -p /mnt&#xA;Limity grupy na /mnt (/dev/loop30) są włączone&#xA;Limity użytkownika na /mnt (/dev/loop30) są włączone&#xA;Limity project na /mnt (/dev/loop30) są włączone&#xA;I działa.&#xA;&#xA;Linki:&#xA;&#xA;(trochę stare już) https://wiki.archlinux.org/title/diskquota&#xA;(trochę nowsze) https://www.digitalocean.com/community/tutorials/how-to-set-filesystem-quotas-on-ubuntu-20-04&#xA;(magiczny feature, o którym tak niewiele) https://ext4.wiki.kernel.org/index.php/Quota&#xA;&#xA;#linux #quota #administration&#xA;&#xA;@arkr]]&gt;</description>
      <content:encoded><![CDATA[<p>Z jakiś powodów zapragnąłem ustawić w Linuksie limity na użycie dysku. Pominę opis poszukiwania informacji w Internecie, ale w skrócie – z nieznanych mi powodów w zdecydowanej przewadze opisywane są jakieś starocie. W artykule pokrótce jak to wygląda w aktualnych wersjach jądra. </p>

<h2 id="kiedyś">Kiedyś</h2>

<p>Kiedyś było tak: poza filesystemem XFS, informacje o użyciu dysku i inodach był zapisywany w aplikach <code>quota.user</code> i <code>quota.group</code>. Potem coś poprawiono i nowe pliki nazywają się <code>aquota.user</code> i <code>aquota.group</code>.</p>

<p>Żeby limitowanie działało, dysk należy zamontować z opcjami <code>usrquota</code> i <code>grpquota</code>. Kolejna nowość wprowadziła usprawnienie dotyczące sposobu rejestrowania aktualnego użycia dysku dla użytkownika. Opcje montowania zmieniły się na <code>usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv1</code> i dzięki temu pliki ze zużyciem dysku aktualizowane razem ze zmianami w plikach i przechodzą przez journal.</p>

<p>Potem trzeba uruchomić</p>

<pre><code class="language-bash">sudo quotacheck -cugv /mnt/   
</code></pre>

<p>i po</p>

<pre><code class="language-bash">sudo quotaon -ug /mnt
</code></pre>

<p>możliwość limitowania dysku jest włączona.</p>

<p>Ale <code>quotacheck</code> wypisało mi komunikat:</p>

<pre><code>quotacheck: Your kernel probably supports ext4 quota feature but you are using external quota files. Please switch your filesystem to use ext4 quota feature as external quota files on ext4 are deprecated.
</code></pre>

<p>I tak dowiedziałem się, że ext4 wspiera pliki limitów jako ukryte inody, tylko trzeba tę opcję włączyć.</p>

<h2 id="a-tak-to-wygląda-teraz">A tak to wygląda teraz</h2>

<p>Zatem, jak to wygląda w na nowym jądrze.</p>

<p>Założenia: mam dysk... np. <code>/dev/loop30</code>, montuję go na <code>/mnt</code>.</p>

<p>Kernel został zbudowany z opcjami
* CONFIG<em>QUOTA=y
* CONFIG</em>QUOTA_TREE=m</p>

<p>Mamy narzędzia do obsługi limitów (np. <code>apt install quota</code> na Ubuntu)</p>

<p>Mamy niezbędne moduły kernela (<code>sudo modprobe quota_v2</code> działa).</p>

<p>Procedura jest następująca:</p>

<p>Tworzę filesystem (ext4):</p>

<pre><code class="language-bash">sudo mkfs.ext4 /dev/loop30
</code></pre>

<p>Włączam wewnętrzną obsługę limitów (to jest ta najnowsza nowość)</p>

<pre><code class="language-bash">sudo tune2fs -O quota /dev/loop30
</code></pre>

<p>Feature <code>quota</code> oznacza: “Enable internal file system quota inodes”</p>

<p>A teraz jeszcze jedno: można mieć nie tylko limity <em>per user</em> i <em>per grupa</em> ale również limit <em>per projekt</em>. Nie mam pojęcia jak działa to ostatnie, ale można je wszystkie niezależnie włączać i wyłączać (patrz <code>man tune2fs</code>):</p>

<pre><code class="language-bash">sudo tune2fs -Q usrquota,grpquota,prjquota /dev/loop30
</code></pre>

<p>Można sprawdzić, że limity zostały włączone wywołując:</p>

<pre><code class="language-bash">sudo tune2fs -l /dev/loop30 | grep &#34;Filesystem features&#34;

Filesystem features:      has_journal ext_attr resize_inode dir_index filetype extent 64bit flex_bg sparse_super large_file huge_file dir_nlink extra_isize quota metadata_csum project
</code></pre>

<p>Obecność słowa “quota” oznacza, że feature jest włączony.</p>

<p>Kolejny krok: montowanie filesystemu:</p>

<pre><code class="language-bash">sudo mount -o usrquota,grpquota,prjquota /dev/loop30 /mnt
</code></pre>

<p>Pewnie dobrze jest sobie ten mountpoint wpisać do /etc/fstab, ale chodzi po prostu o to, żeby włączyć dla tego dysku odpowiednie zliczanie limitów.</p>

<p>Żadnych plików limitów w /mnt nie widać, ale możemy uruchomić</p>

<pre><code class="language-bash">sudo repquota /mnt
</code></pre>

<p>I zobaczymy zużycie  tego dysku przez użytkownika root (bo tylko jego obiekty są na razie w tym katalogu).
Przy okazji, <code>repquota</code> z parametrem <code>-g</code> pokaże limity dla grup, a <code>-P</code> limity dla projektów (jak ustalę jak zrobić taki projekt, to na pewno to opiszę).</p>

<p>Limity można ustawić poleceniami <code>setquota</code> i <code>edquota</code> (ale to już standard – każdy manual opisuje jak to zrobić).</p>

<p>Wygląda na to, że jeśli użyliśmy podczas montowania opcji usrquota, grpquta etc, to limity są od razu egzekwowane (bez konieczności włączania ich za pomocą <code>quotaon</code>), co można sprawdzić używając opcji <code>-p</code>:</p>

<pre><code class="language-bash">sudo quotaon -p /mnt
Limity grupy na /mnt (/dev/loop30) są włączone
Limity użytkownika na /mnt (/dev/loop30) są włączone
Limity project na /mnt (/dev/loop30) są włączone
</code></pre>

<p>I działa.</p>

<p>Linki:</p>
<ul><li>(trochę stare już) <a href="https://wiki.archlinux.org/title/disk_quota" rel="nofollow">https://wiki.archlinux.org/title/disk_quota</a></li>
<li>(trochę nowsze) <a href="https://www.digitalocean.com/community/tutorials/how-to-set-filesystem-quotas-on-ubuntu-20-04" rel="nofollow">https://www.digitalocean.com/community/tutorials/how-to-set-filesystem-quotas-on-ubuntu-20-04</a></li>
<li>(magiczny feature, o którym tak niewiele) <a href="https://ext4.wiki.kernel.org/index.php/Quota" rel="nofollow">https://ext4.wiki.kernel.org/index.php/Quota</a></li></ul>

<p><a href="/arek/tag:linux" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">linux</span></a> <a href="/arek/tag:quota" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">quota</span></a> <a href="/arek/tag:administration" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">administration</span></a></p>

<p><a href="https://mastodon.social/@ark_r" rel="nofollow">@ark_r</a></p>
]]></content:encoded>
      <guid>https://baal.ar76.eu/arek/linux-disk-quota</guid>
      <pubDate>Thu, 01 Dec 2022 19:37:22 +0000</pubDate>
    </item>
    <item>
      <title>git commit --fixup=...</title>
      <link>https://baal.ar76.eu/arek/git-commit-fixup</link>
      <description>&lt;![CDATA[Czasami zdarza się, że zatwierdziłem jakieś zmiany, ale coś tam jednak wymaga poprawy. Pewnie skorzystam z git rebase -i, tym niemniej po takich poprawkach uporządkowanie commitów wymaga trochę pracy i tu z pomocą przychodzi opcja --fixup.&#xA;!--more--&#xA;&#xA;Załóżmy, że mam jakieś repozytorium git:&#xA;&#xA;$ git log --oneline&#xA;63594c0 (HEAD -  master) Initial&#xA;W przykładzie jest tylko jeden commit, ale to nie istotne.&#xA;&#xA;Zaczynam więc standardowo:&#xA;&#xA;$ git switch -c feature-1&#xA;$ nano README.md       # Tu wpisuję treść mojego README&#xA;$ git add README.md&#xA;$ git commit -m &#34;Dodano README.md&#34;&#xA;&#xA;W wyniku tych działań mam coś takiego:&#xA;&#xA;$ git log --oneline &#xA;87aeca8 (HEAD -  feature-1) Dodano README.md&#xA;63594c0 (master) Initial&#xA;Powiedzmy, że jednak odkrywam, że w README zrobiłem paskudny błąd ortograficzny:&#xA;&#xA;$ nano README.md     # poprawiam wstydliwy błąd&#xA;$ git add README.md&#xA;$ git commit --amend&#xA;&#xA;Ponowne sprawdzenie logu:&#xA;&#xA;$ git log --oneline &#xA;6f3d411 (HEAD -  feature-1) Dodano README.md&#xA;63594c0 (master) Initial&#xA;Jak widać ostatni commit został przepisany (87aeca8 został zastąpiony przez 6f3d411). Efekt w postaci poprawionego błędu i czystej historii commitów został osiągnięty.&#xA;&#xA;To jest ten prostszy przypadek ale co, jeśli błąd w README.md uświadomię sobie dopiero po jakimś czasie, gdy już zdążyłem dodać inne commity. Np mam taką historię:&#xA;&#xA;$ git log --oneline &#xA;6e8a8dd (HEAD -  feature-1) Dodano main.c&#xA;6f3d411 Dodano README.md&#xA;63594c0 (master) Initial&#xA;&#xA;I dopiero teraz stwierdzam konieczność poprawienia pliku README.md.&#xA;Opcja --amend już nie zadziała i trzeba skorzystać z dobrodziejstw git rebase -i. &#34;Rebase&#34; pozwoli ponownie przeedytować istniejące commity, choć każdy kto to robił wie, że będzie trochę zabawy. &#xA;I tu wracam do wspomnianej opcji --fixup operacji commit.&#xA;&#xA;Mogę zrobić następującą rzecz:&#xA;&#xA;$ nano README.md     # poprawiam (kolejny pewnie) błąd ortograficzny&#xA;$ git add README.md&#xA;$ git commit --fixup=6f3d411   # wskazuję poprawiany commit&#xA;Poprawiany commit, to ten z opisem &#34;Dodano README.md&#34;. Polecenie nie pyta mnie nawet o komentarz i mam następującą historię:&#xA;&#xA;$ git log --oneline &#xA;9fadf75 (HEAD -  feature-1) fixup! Dodano README.md&#xA;6e8a8dd Dodano main.c&#xA;6f3d411 Dodano README.md&#xA;63594c0 (master) Initial&#xA;&#xA;I teraz ta fajna część:&#xA;&#xA;$ git rebase -i --autosquash master&#xA;Otwiera się edytor umożliwiający ustawienie poleceń odnośnie commitów następujących po master (commity pojawiają się od najstarszego do najnowszego):&#xA;&#xA;pick 6f3d411 Dodano README.md&#xA;fixup 9fadf75 fixup! Dodano README.md&#xA;pick 6e8a8dd Dodano main.c&#xA;&#xA;Widać, że &#34;fixup! Dodano README.md&#34; zostało umieszczone zaraz po &#34;Dodano README.md&#34; i ma automatycznie (dzięki opcji --autosquash) wybrane polecenie fixup. Po zamknięciu edytora powstaje taka oto historia commitów:&#xA;&#xA;$ git log --oneline &#xA;8abcc8e (HEAD -  feature-1) Dodano main.c&#xA;3c9672d Dodano README.md&#xA;63594c0 (master) Initial&#xA;&#xA;Commity powyżej &#34;63594c0&#34; zostały przepisane, w tym &#34;Dodano README.md&#34;  zebrał zmiany, które dodałem w &#34;fixup! Dodano README.md&#34;.&#xA;&#xA;Błędy poprawione, historia czysta. Można robić git merge.&#xA;&#xA;@arkr]]&gt;</description>
      <content:encoded><![CDATA[<p>Czasami zdarza się, że zatwierdziłem jakieś zmiany, ale coś tam jednak wymaga poprawy. Pewnie skorzystam z <code>git rebase -i</code>, tym niemniej po takich poprawkach uporządkowanie commitów wymaga trochę pracy i tu z pomocą przychodzi opcja <code>--fixup</code>.
</p>

<p>Załóżmy, że mam jakieś repozytorium git:</p>

<pre><code class="language-bash">$ git log --oneline
63594c0 (HEAD -&gt; master) Initial
</code></pre>

<p>W przykładzie jest tylko jeden commit, ale to nie istotne.</p>

<p>Zaczynam więc standardowo:</p>

<pre><code class="language-bash">$ git switch -c feature-1
$ nano README.md       # Tu wpisuję treść mojego README
$ git add README.md
$ git commit -m &#34;Dodano README.md&#34;
</code></pre>

<p>W wyniku tych działań mam coś takiego:</p>

<pre><code class="language-bash">$ git log --oneline 
87aeca8 (HEAD -&gt; feature-1) Dodano README.md
63594c0 (master) Initial
</code></pre>

<p>Powiedzmy, że jednak odkrywam, że w README zrobiłem paskudny błąd ortograficzny:</p>

<pre><code class="language-bash">$ nano README.md     # poprawiam wstydliwy błąd
$ git add README.md
$ git commit --amend
</code></pre>

<p>Ponowne sprawdzenie logu:</p>

<pre><code class="language-bash">$ git log --oneline 
6f3d411 (HEAD -&gt; feature-1) Dodano README.md
63594c0 (master) Initial
</code></pre>

<p>Jak widać ostatni commit został przepisany (87aeca8 został zastąpiony przez 6f3d411). Efekt w postaci poprawionego błędu i czystej historii commitów został osiągnięty.</p>

<p>To jest ten prostszy przypadek ale co, jeśli błąd w README.md uświadomię sobie dopiero po jakimś czasie, gdy już zdążyłem dodać inne commity. Np mam taką historię:</p>

<pre><code class="language-bash">$ git log --oneline 
6e8a8dd (HEAD -&gt; feature-1) Dodano main.c
6f3d411 Dodano README.md
63594c0 (master) Initial
</code></pre>

<p>I dopiero teraz stwierdzam konieczność poprawienia pliku README.md.
Opcja <code>--amend</code> już nie zadziała i trzeba skorzystać z dobrodziejstw <code>git rebase -i</code>. “Rebase” pozwoli ponownie przeedytować istniejące commity, choć każdy kto to robił wie, że będzie trochę zabawy.
I tu wracam do wspomnianej opcji <code>--fixup</code> operacji commit.</p>

<p>Mogę zrobić następującą rzecz:</p>

<pre><code class="language-bash">$ nano README.md     # poprawiam (kolejny pewnie) błąd ortograficzny
$ git add README.md
$ git commit --fixup=6f3d411   # wskazuję poprawiany commit
</code></pre>

<p>Poprawiany commit, to ten z opisem “Dodano README.md”. Polecenie nie pyta mnie nawet o komentarz i mam następującą historię:</p>

<pre><code class="language-bash">$ git log --oneline 
9fadf75 (HEAD -&gt; feature-1) fixup! Dodano README.md
6e8a8dd Dodano main.c
6f3d411 Dodano README.md
63594c0 (master) Initial
</code></pre>

<p>I teraz ta fajna część:</p>

<pre><code class="language-bash">$ git rebase -i --autosquash master
</code></pre>

<p>Otwiera się edytor umożliwiający ustawienie poleceń odnośnie commitów następujących po <code>master</code> (commity pojawiają się od najstarszego do najnowszego):</p>

<pre><code>pick 6f3d411 Dodano README.md
fixup 9fadf75 fixup! Dodano README.md
pick 6e8a8dd Dodano main.c
</code></pre>

<p>Widać, że “fixup! Dodano README.md” zostało umieszczone zaraz po “Dodano README.md” i ma automatycznie (dzięki opcji <code>--autosquash</code>) wybrane polecenie <code>fixup</code>. Po zamknięciu edytora powstaje taka oto historia commitów:</p>

<pre><code class="language-bash">$ git log --oneline 
8abcc8e (HEAD -&gt; feature-1) Dodano main.c
3c9672d Dodano README.md
63594c0 (master) Initial
</code></pre>

<p>Commity powyżej “63594c0” zostały przepisane, w tym “Dodano README.md”  zebrał zmiany, które dodałem w “fixup! Dodano README.md”.</p>

<p>Błędy poprawione, historia czysta. Można robić <code>git merge</code>.</p>

<p><a href="https://mastodon.social/@ark_r" rel="nofollow">@ark_r</a></p>
]]></content:encoded>
      <guid>https://baal.ar76.eu/arek/git-commit-fixup</guid>
      <pubDate>Mon, 14 Nov 2022 14:09:11 +0000</pubDate>
    </item>
    <item>
      <title>O optymalizacji i wydajności</title>
      <link>https://baal.ar76.eu/arek/o-optymalizacji-i-wydajnosci</link>
      <description>&lt;![CDATA[Albo &#34;o co chodzi benchmarkach&#34;.&#xA;&#xA;Istnieje sobie portal benchmarksgame-team.pages.debian.net, który na przykładzie kilku algorytmów pozwala porównać wydajność wydajność implementacji w różnych językach.&#xA;&#xA;W swojej historii pisałem w wielu różnych językach i niejednokrotnie mam możliwość wybrania języka dla realizowanego projektu. Takie porównania są więc interesujące, bo w sytuacji, gdy gdzieś kluczowa jest wydajność, dobrze jest wiedzieć, czy wybrałem właściwe narzędzie albo jak bardzo mój wybór jest nieoptymalny.&#xA;&#xA;Z różnych powodów interesuje mnie porównanie wydajności języków Java i Go. Wchodząc pod ten adres https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/go.html można się przekonać, że Go jest ogólnie porównywalny z Javą albo nawet szybszy, poza jednym przypadkiem: Binary trees!&#xA;&#xA;Wydajność benchmarku binary-trees dla implementacji w Java i Go&#xA;&#xA;Przypadki, gdy coś jest 10% szybsze albo wolniejsze, coś oczywiście mówią, ale w realnych programach najprawdopodobniej takie zyski rozmyją się w natłoku innych, niezbędnych i nieuniknionych elementów programu. W praktyce oprogramowanie, które piszę, co chwila coś wysyła po sieci, albo czeka na rezultaty z sieci i sekunda więcej na obliczeniach (choć ważna) nie boli tak bardzo. Zawsze można dodać CPU lub kolejną maszynę wirtualną i ogólna wydajność systemu spełni wymagania.&#xA;&#xA;Ale w tym wypadku jest dramat: implementacja w Go jest 5,6 razy wolniejsza.&#xA;&#xA;!--more--&#xA;&#xA;Przyjrzyjmy się więc kodom źródłowym najszybszej implementacji Go.&#xA;&#xA;package main&#xA;import (&#xA;   &#34;flag&#34;&#xA;   &#34;fmt&#34;&#xA;   &#34;strconv&#34;&#xA;   &#34;sync&#34;&#xA;)&#xA;type Node struct {&#xA;   left, right Node&#xA;}&#xA;var pool = sync.Pool {&#xA;     New: func() interface{} {&#xA;          return &amp;Node{}&#xA;     },&#xA;}&#xA;const minDepth = 4&#xA;func trees(maxDepth int) {&#xA;   longLastingNode := createTree(maxDepth)&#xA;   depth := 4&#xA;   for depth &lt;= maxDepth {&#xA;      iterations := 1 &lt;&lt; uint(maxDepth-depth+minDepth) // 16 &lt;&lt; (maxDepth - depth)&#xA;      loops(iterations, depth)&#xA;      depth += 2&#xA;   }&#xA;   fmt.Printf(&#34;long lived tree of depth %d\t check: %d\n&#34;, maxDepth,&#xA;      checkTree(longLastingNode))&#xA;}&#xA;func loops(iterations, depth int) {&#xA;   check := 0&#xA;   item := 0&#xA;   for item &lt; iterations {&#xA;      t := createTree(depth)&#xA;      check += checkTree(t)&#xA;      pool.Put(t)&#xA;      item++&#xA;   }&#xA;   fmt.Printf(&#34;%d\t trees of depth %d\t check: %d\n&#34;,&#xA;      iterations, depth, check)&#xA;}&#xA;func checkTree(n Node) int {&#xA;   if n.left == nil {&#xA;      // parent will sync.Pool.Put&#xA;      return 1&#xA;   }&#xA;   check := checkTree(n.left) + checkTree(n.right) + 1&#xA;   pool.Put(n.left)&#xA;   n.left = nil&#xA;   pool.Put(n.right)&#xA;   n.right = nil&#xA;   return check&#xA;}&#xA;func createTree(depth int) Node {&#xA;   node := pool.Get().(Node)&#xA;   if depth   0 {&#xA;      depth--&#xA;      node.left = createTree(depth)&#xA;      node.right = createTree(depth)&#xA;   }&#xA;   return node&#xA;}&#xA;func main() {&#xA;   n := 0&#xA;   flag.Parse()&#xA;   if flag.NArg()   0 {&#xA;      n,  = strconv.Atoi(flag.Arg(0))&#xA;   }&#xA;   maxDepth := n&#xA;   if minDepth+2   n {&#xA;      maxDepth = minDepth + 2&#xA;   }&#xA;   {&#xA;      stretchDepth := maxDepth + 1&#xA;      t := createTree(stretchDepth)&#xA;      check := checkTree(t)&#xA;      pool.Put(t)&#xA;      fmt.Printf(&#34;stretch tree of depth %d\t check: %d\n&#34;, stretchDepth, check)&#xA;   }&#xA;   trees(maxDepth)&#xA;}&#xA;&#xA;Powyższy program tworzy wielokrotnie drzewo binarne, czyli strukturę składającą się z wielu węzłów (Node), z których każdy ma dwa wskaźniki łączące węzeł z dokładnie dwoma węzłami potomnymi — lewym i prawym.&#xA;&#xA;Trzy poziomy drzewa binarnego pokazano na poniższym rysunku.&#xA;&#xA;Drzewo binarne&#xA;&#xA;Za tworzenie drzewa odpowiada rekurencyjna funkcja createTree a następnie liczba węzłów w drzewie jest liczona za pomocą funkcją checkTree, która jednocześnie dokonuje destrukcji drzewa zwalniając wszystkie węzły.&#xA;&#xA;Operacja tworzenia i niszczenia drzewa wykonywana jest wielokrotnie (funkcja loops) z kolejno zwiększającą się wysokością drzewa. Liczba iteracji wyznaczana jest w funkcji trees, która dodatkowo alokuje jedno drzewo o maksymalnej wskazanej głębokości na cały czas testu.&#xA;&#xA;Jak nie trudno się domyślić, benchmark testuje wydajność i efektywność alokacji pamięci na stercie.&#xA;&#xA;Mamy tu dwie kwestie:&#xA;&#xA;Szybkość alokacji wielu małych obiektów&#xA;Obsługa zwalniania pamięci.&#xA;&#xA;Taki test jest szczególnie obciążający dla platform wykorzystujących Garbage Collector. Mamy setki tysięcy małych obiektów z referencjami między sobą. To powoduje, że podczas odśmiecania, GC musi przebiec po wszystkich tych obiektach, żeby ustalić, które z nich są żywe (tj. gdzieś w programie jest odwołanie do drzewa obiektów, co oznacza, że należy zachować je przy życiu), a które już nie posiadają odwołań, co oznacza, że pamięć przez nie zajmowana może być oznaczona jako wolna i posłużyć w kolejnych alokacjach.&#xA;&#xA;Wiedząc to wszystko, można spokojnie przyjąć różnica w czasie wykonania programu w Java i Go jednoznacznie wskazuje, że Java ma prawie 6 razy lepszy Garbage Collector niż Go.&#xA;&#xA;I w pewnym sensie tak jest. Java wykorzystuje generacyjny kolektor kompaktujący. To oznacza, że nowe obiekty tworzone są w specjalnym obszarze pamięci (“eden”), która jest zajmowana na zasadzie stosu (tj. istnieje wskaźnik, który mówi gdzie kończy się pamięć zajęta, a zaczyna wolna i każda alokacja kolejnego obiektu polega wyłącznie na przesunięciu tego wskaźnika). Gdy zajęty został cały obszar pamięci dla nowych obiektów, GC przebiega po nich sprawdzając, które są wciąż żywe i kopiuje je do innego obszaru pamięci, wskaźnik ustawiany jest znów na początek i mamy wolną pamięć na kolejne alokacje (bardzo to upraszczam, ale idea jest mniej więcej wyjaśniona).&#xA;&#xA;To oznacza oczywiście, że JVM zmienia adresy zaalokowanych obiektów i trzeba zatrzymać działanie programu i zaktualizować wskaźniki, które na nie wskazują, ale sama alokacja jest faktycznie bardzo szybka.&#xA;&#xA;W Go GC działa inaczej — nie ma generacji, czyli sekcji “nowe obiekty” i “stare obiekty” (Java ma trzy generacje). Raz zaalokowany obiekt nigdy nie zmienia swojego adresu ale alokacja tych obiektów trwa dłużej — trzeba po prostu znaleźć dla nich miejsce na stercie.&#xA;&#xA;Nie ma przesuwania obiektów, nie ma kompaktowania sterty (czyli przesuwania obiektów tak, żeby odzyskać duże obszary wolnej pamięci), ale za to w większym stopniu możliwe jest działania GC równolegle do właściwego programu.&#xA;&#xA;Benchmark wydaje się jednak pokazywać, że rozwiązanie Go się nie sprawdza — podejście Java jest dużo efektywniejsze. Ta wersja mikrobenchmarku Go korzysta nawet z puli obiektów (sync.Pool — obiekty, które nie są już potrzebne, są zwracane do puli, żeby zmniejszyć liczbę alokacji na stercie) i dalej jest wściekle wolna, w porównaniu do Javy.&#xA;&#xA;Co zatem Go robi źle?&#xA;&#xA;Otóż nic — ma zupełnie inne podejście. W Javie praktycznie nie ma innej możliwości niż zaalokowanie obiektu na stercie. Jeśli robię tablicę 100 obiektów typu “MojaKlasa”, to na stercie alokowanych jest 100 niezależnych obiektów oraz sto pierwszy obiekt — sama tablica. Tablica trzyma 100 wskaźników do moich 100 obiektów. Ponieważ każde utworzenie obiektu w Javie musi odbyć się na stercie, GC Javy ma wyrafinowane mechanizmy zapewniające, że te alokacje są bardzo efektywne. Nie dotyczy to wyłącznie typów podstawowych (np. intlub char), które w takiej tablicy zostaną zaalokowane jako ciągły obszar bajtów (referencje technicznie też są zrealizowane jako wskaźniki, które można potraktować jako typy proste, ale Java nie daje żadnego dostępu do tych wskaźników, poza oczywiście referencją do wskazywanego obiektu).&#xA;&#xA;W Go tak nie jest.&#xA;&#xA;Go ma kilka typów referencyjnych (np. chan, slice, map albo string), ale posiada też wskaźniki oraz value types! W Javie tylko zmienne typu int, bool, char etc są wartościami. W Go prawie wszystko jest wartością. Chyba, że potrzebujemy wskaźnika, wtedy wyrażamy to wprost, np var intPtr int. Ta gwiazdka oznacza, że zmienna intPtr jest wskaźnikiem na wartość int. Zamiast int może być jednak dowolny obiekt — np. nasza struktura Node z powyższego programu.&#xA;&#xA;Kolejna ważna rzecz — w Go można pobrać wskaźnik do wszystkiego co jest adresowalne. A większość obiektów jest: można mieć adres funkcji, adres dowolnej zmiennej, ale też co jest istotne — adres do składowej naszej struktury albo adres do elementu tablicy.&#xA;&#xA;Na przykład:&#xA;&#xA;type Moja struct {&#xA;  a int&#xA;  b int&#xA;}&#xA;// ....&#xA;// &#34;moja&#34; jest zmienną o rozmiarze dwu int-ów i jest to wartość.&#xA;// w przykładzie poniżej zostanie zaalokowana na stosie - tj. w ogóle nie trafia na stertę&#xA;moja := Moja{&#xA;  a: 1,&#xA;  b: 2,&#xA;}&#xA;// dzięki użyciu operatora &amp;, zmienna mojaPtr jest wskaźnikiem do obiektu typu Moja. Obiekt został zaalokowany na stercie.&#xA;mojaPtr := &amp;Moja{&#xA;  a: 10,&#xA;  b: 20,&#xA;}&#xA;// zmieniam wartość wskaźnika mojaPtr i przypisuję do niego adres obiektu &#34;moja&#34; (tego utworzone wcześniej)&#xA;mojaPtr = &amp;moja&#xA;// i tu najlepsze mogę tez pobrać adres składowej obiektu:&#xA;var intPtr int&#xA;intPtr = &amp;moja.b&#xA;// i tablice - tablica 10 obiektów Moja (pojedynczy, ciągły blok &#xA;// pamięci, w którym mieści się 10 obiektów Moja, czyli 20 int-ów&#xA;var tab [10]Moja&#xA;// mojaPtr wskazuje na ostatni element tablicy&#xA;mojaPtr = &amp;tab[9]&#xA;&#xA;Te cechy języka powodują, że:&#xA;&#xA;Nie jest potrzebny specjalny “eden” na stercie, ponieważ krótko żyjące obiekty mogą być z powodzeniem tworzone na stosie, który będzie pełnił taką samą rolę.&#xA;&#xA;Zaalokowanie tablicy obiektów (nie ważne, czy na stosie, czy na stercie) gwarantuje, że te obiekty są wewnątrz ciągłego bloku pamięci i mogę odwoływać się do nich przez wskaźniki. Co jest fajne — jeśli sekwencyjnie iteruję po obiektach w ciągłym bloku RAM, CPU potrafi dużo efektywniej wykorzystywać cache, niż w przypadku, gdy skaczę chaotycznie po randomowych adresach.&#xA;&#xA;3 Znacznie rzadziej występuje potrzeba alokowania obiektów na stercie, a dodatkowo mogę je tam alokować całymi grupami, a nie tylko pojedynczo.&#xA;&#xA;Skoro tak, to czemu nie wykorzystać tych właściwości. Zmieniłem program w taki oto sposób:&#xA;&#xA;package main&#xA;import (&#xA; &#34;flag&#34;&#xA; &#34;fmt&#34;&#xA; &#34;strconv&#34;&#xA;)&#xA;type Node struct {&#xA;   left, right Node&#xA;}&#xA;// --- Istotne zmiany:&#xA;var pool = NewArenaNode&#xA;type Arena[N any] struct {&#xA; nodes []N&#xA; chunk int&#xA;}&#xA;func NewArenaN any Arena[N] {&#xA; return Arena[N] {&#xA;  chunk: size,&#xA;  nodes: make([]N, 0, size),&#xA; }&#xA;}&#xA;func (arena Arena[N]) New() N {&#xA;    if len(arena.nodes) == 0 {&#xA;        arena.nodes = make([]N, arena.chunk)&#xA;    }&#xA;    n := &amp;(arena.nodes)[len(arena.nodes)-1]&#xA;    arena.nodes = (arena.nodes)[:len(arena.nodes)-1]&#xA;    return n&#xA;}&#xA;// -- koniec&#xA;const minDepth = 4&#xA;func trees(maxDepth int) {&#xA;   longLastingNode := createTree(maxDepth)&#xA;   depth := 4&#xA;   for depth &lt;= maxDepth {&#xA;      iterations := 1 &lt;&lt; uint(maxDepth-depth+minDepth) // 16 &lt;&lt; (maxDepth - depth)&#xA;   loops(iterations, depth)&#xA;      depth += 2&#xA;   }&#xA;   fmt.Printf(&#34;long lived tree of depth %d\t check: %d\n&#34;, maxDepth,&#xA;      checkTree(longLastingNode))&#xA;}&#xA;func loops(iterations, depth int) {&#xA;   check := 0&#xA;   item := 0&#xA;   for item &lt; iterations {&#xA;      t := createTree(depth)&#xA;      check += checkTree(t)&#xA;      item++&#xA;   }&#xA;   fmt.Printf(&#34;%d\t trees of depth %d\t check: %d\n&#34;,&#xA;      iterations, depth, check)&#xA;}&#xA;func checkTree(n Node) int {&#xA;   if n.left == nil {&#xA;      // parent will sync.Pool.Put&#xA;      return 1&#xA;   }&#xA;   check := checkTree(n.left) + checkTree(n.right) + 1&#xA;   n.left = nil&#xA;   n.right = nil&#xA;   return check&#xA;}&#xA;func createTree(depth int) *Node {&#xA;   node := pool.New()&#xA;   if depth   0 {&#xA;      depth--&#xA;      node.left = createTree(depth)&#xA;      node.right = createTree(depth)&#xA;   }&#xA;   return node&#xA;}&#xA;func main() {&#xA;   n := 0&#xA;   flag.Parse()&#xA;   if flag.NArg()   0 {&#xA;      n,  = strconv.Atoi(flag.Arg(0))&#xA;   }&#xA;   maxDepth := n&#xA;   if minDepth+2   n {&#xA;      maxDepth = minDepth + 2&#xA;   }&#xA;   {&#xA;      stretchDepth := maxDepth + 1&#xA;      t := createTree(stretchDepth)&#xA;      check := checkTree(t)&#xA;      fmt.Printf(&#34;stretch tree of depth %d\t check: %d\n&#34;, stretchDepth, check)&#xA;   }&#xA;   trees(maxDepth)&#xA;}&#xA;&#xA;Istotne zmiany oznaczyłem komentarzem. Reszta była tylko prostym dostosowaniem kodu do nowego wywołania New() i usunięciem zbędnych w tej wersji wywołań pool.Put.&#xA;&#xA;Nie będę szczegółowo opisywał na czym polega ten kod (używa generyków z go 1.18! :) ) ale istotniejsza jest idea. Nowy obiekt w go można utworzyć np .tak:&#xA;&#xA;node := new(Node)&#xA;&#xA;albo tak:&#xA;&#xA;node := &amp;Node{}&#xA;&#xA;W obu wypadkach w zmiennej node pojawi się wskaźnik do utworzonego na stercie obiektu Node.&#xA;&#xA;Jedno takie wywołanie, to jedna alokacja. Więc robię małą sztuczkę — stosuję własny alokator, który od zaalokuje tablicę 1000 obiektów node i zwróci wskaźnik do jednego z nich.&#xA;&#xA;Moja metoda New zwraca obiekt z zaalokowanej tablicy (jest to wskaźnik na ostatni element tablicy):&#xA;&#xA;n := &amp;(arena.nodes)[len(arena.nodes)-1]&#xA;return n&#xA;&#xA;Kolejne tworzenie obiektu nie powoduje już alokacji następnego obiektu ze sterty, tylko zwróci następny element z uprzednio zaalokowanej tablicy. Po 1000 takich wywołań stworzona zostanie kolejna tablica z tysiącem elementów. Efekt tego jest taki, że jest 1000 razy mniej alokacji i 1000 razy mnie obiektów na stercie (choć obiekty są 1000 razy większe).&#xA;&#xA;Jaki będzie tego efekt? Na moim komputerze oryginalny program wykonywał się 21,550 sekund:&#xA;&#xA;time ./binary-trees-6 21&#xA;stretch tree of depth 22  check: 8388607&#xA;2097152  trees of depth 4  check: 65011712&#xA;524288  trees of depth 6  check: 66584576&#xA;131072  trees of depth 8  check: 66977792&#xA;32768  trees of depth 10  check: 67076096&#xA;8192  trees of depth 12  check: 67100672&#xA;2048  trees of depth 14  check: 67106816&#xA;512  trees of depth 16  check: 67108352&#xA;128  trees of depth 18  check: 67108736&#xA;32  trees of depth 20  check: 67108832&#xA;long lived tree of depth 21  check: 4194303&#xA;real 0m21,550s&#xA;user 0m22,448s&#xA;sys 0m0,156s&#xA;&#xA;A moja poprawiona wersja:&#xA;&#xA;time ./binary-trees-ar 21&#xA;stretch tree of depth 22  check: 8388607&#xA;2097152  trees of depth 4  check: 65011712&#xA;524288  trees of depth 6  check: 66584576&#xA;131072  trees of depth 8  check: 66977792&#xA;32768  trees of depth 10  check: 67076096&#xA;8192  trees of depth 12  check: 67100672&#xA;2048  trees of depth 14  check: 67106816&#xA;512  trees of depth 16  check: 67108352&#xA;128  trees of depth 18  check: 67108736&#xA;32  trees of depth 20  check: 67108832&#xA;long lived tree of depth 21  check: 4194303&#xA;real 0m4,384s&#xA;user 0m8,965s&#xA;sys 0m0,142s&#xA;&#xA;Czyli jest 4,9 razy szybszy!&#xA;&#xA;Niezły wynik. Ale moje rozwiązanie nie jest jakoś szczególnie odkrywcze, więc dlaczego nikt na to nie wpadł i nie przesłał takiej wersji programu?&#xA;&#xA;Otóż wpadłem na to kilka lat temu i wysłałem taką poprawioną wersję (nie było wtedy jeszcze generyków), ale została odrzucona, ponieważ regulamin (czy jak to nazwać) mówi, że nie można stosować dedykowanych/specyficznych alokatorów.&#xA;&#xA;Nie wiem, czy wersja “generyczna” alokatora (tak jak ją przedstawiłem) już spełniałaby kryteria, ale to jest bez znaczenia, bo zrozumiałem wtedy, że celem tych benchmarków jest pokazanie silnych i słabych stron platformy, a nie odpowiedź na pytanie, który język jest “szybszy”.&#xA;&#xA;Z tego można wyciągnąć parę wniosków:&#xA;&#xA;Nie kłóć się o to, który język jest szybszy, podpierając mikrobenchmarkami, bo nie taki jest ich cel i zawsze może się okazać, że ktoś w Pythonie stworzy szybszą implementację danego algorytmu niż Ty w C++ (albo assemblerze — niektórzy do dziś wierzą, że “assembler jest najszybszy”).&#xA;Mikrobenchmarki pokazują silne i słabe strony platformy — jak widzisz, że któraś z platform (runtime, maszyna wirtualna) w czymś niedomaga, to oznacza to, że nie należy próbować czegoś w ten sposób używać i należy znaleźć jakieś obejście. Każda platforma ma jakieś słabsze strony.&#xA;Nie pisz w Go programu będącego kalką kodu Java (ani C, ani C++). Każdy język ma swoją specyfikę, swoje idiomy. Należy je zrozumieć i wykorzystać wszystko to, co dany język ma najlepszego. Prawdopodobnie niektóre algorytmy mogą być w jakimś języku w ogóle trudne do efektywnej implementacji. Może należy wtedy użyć zupełnie innego algorytmu? Ważne jest osiągnięcie celu — droga do niego może być dowolna.&#xA;&#xA;Tym niemniej przyspieszenie czego ponad 5 razy zawsze cieszy.&#xA;&#xA;Dzięki, jeśli doszedłeś aż do tego miejsca ;)&#xA;&#xA;Jeśli masz jakiś uwagi, napisz na a rel=&#34;me&#34; href=&#34;https://mastodon.social/@arkr&#34; @arkr@mastodon.social /a&#xA;&#xA;#go #golang &#xA;#optymalizacja #benchmark&#xA;&#xA;@arkr]]&gt;</description>
      <content:encoded><![CDATA[<p><em>Albo “o co chodzi benchmarkach”.</em></p>

<p>Istnieje sobie portal <a href="https://benchmarksgame-team.pages.debian.net/benchmarksgame/index.html" rel="nofollow">benchmarksgame-team.pages.debian.net</a>, który na przykładzie kilku algorytmów pozwala porównać wydajność wydajność implementacji w różnych językach.</p>

<p>W swojej historii pisałem w wielu różnych językach i niejednokrotnie mam możliwość wybrania języka dla realizowanego projektu. Takie porównania są więc interesujące, bo w sytuacji, gdy gdzieś kluczowa jest wydajność, dobrze jest wiedzieć, czy wybrałem właściwe narzędzie albo jak bardzo mój wybór jest nieoptymalny.</p>

<p>Z różnych powodów interesuje mnie porównanie wydajności języków Java i Go. Wchodząc pod ten adres <a href="https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/go.html" rel="nofollow">https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/go.html</a> można się przekonać, że Go jest ogólnie porównywalny z Javą albo nawet szybszy, poza jednym przypadkiem: Binary trees!</p>

<p><img src="/assets/bench.png" alt="Wydajność benchmarku binary-trees dla implementacji w Java i Go"></p>

<p>Przypadki, gdy coś jest 10% szybsze albo wolniejsze, coś oczywiście mówią, ale w realnych programach najprawdopodobniej takie zyski rozmyją się w natłoku innych, niezbędnych i nieuniknionych elementów programu. W praktyce oprogramowanie, które piszę, co chwila coś wysyła po sieci, albo czeka na rezultaty z sieci i sekunda więcej na obliczeniach (choć ważna) nie boli tak bardzo. Zawsze można dodać CPU lub kolejną maszynę wirtualną i ogólna wydajność systemu spełni wymagania.</p>

<p>Ale w tym wypadku jest dramat: implementacja w Go jest 5,6 razy wolniejsza.</p>



<p>Przyjrzyjmy się więc kodom źródłowym najszybszej implementacji Go.</p>

<pre><code class="language-go">package main
import (
   &#34;flag&#34;
   &#34;fmt&#34;
   &#34;strconv&#34;
   &#34;sync&#34;
)
type Node struct {
   left, right *Node
}
var pool = sync.Pool {
     New: func() interface{} {
          return &amp;Node{}
     },
}
const minDepth = 4
func trees(maxDepth int) {
   longLastingNode := createTree(maxDepth)
   depth := 4
   for depth &lt;= maxDepth {
      iterations := 1 &lt;&lt; uint(maxDepth-depth+minDepth) // 16 &lt;&lt; (maxDepth - depth)
      loops(iterations, depth)
      depth += 2
   }
   fmt.Printf(&#34;long lived tree of depth %d\t check: %d\n&#34;, maxDepth,
      checkTree(longLastingNode))
}
func loops(iterations, depth int) {
   check := 0
   item := 0
   for item &lt; iterations {
      t := createTree(depth)
      check += checkTree(t)
      pool.Put(t)
      item++
   }
   fmt.Printf(&#34;%d\t trees of depth %d\t check: %d\n&#34;,
      iterations, depth, check)
}
func checkTree(n *Node) int {
   if n.left == nil {
      // parent will sync.Pool.Put
      return 1
   }
   check := checkTree(n.left) + checkTree(n.right) + 1
   pool.Put(n.left)
   n.left = nil
   pool.Put(n.right)
   n.right = nil
   return check
}
func createTree(depth int) *Node {
   node := pool.Get().(*Node)
   if depth &gt; 0 {
      depth--
      node.left = createTree(depth)
      node.right = createTree(depth)
   }
   return node
}
func main() {
   n := 0
   flag.Parse()
   if flag.NArg() &gt; 0 {
      n, _ = strconv.Atoi(flag.Arg(0))
   }
   maxDepth := n
   if minDepth+2 &gt; n {
      maxDepth = minDepth + 2
   }
   {
      stretchDepth := maxDepth + 1
      t := createTree(stretchDepth)
      check := checkTree(t)
      pool.Put(t)
      fmt.Printf(&#34;stretch tree of depth %d\t check: %d\n&#34;, stretchDepth, check)
   }
   trees(maxDepth)
}
</code></pre>

<p>Powyższy program tworzy wielokrotnie drzewo binarne, czyli strukturę składającą się z wielu węzłów (Node), z których każdy ma dwa wskaźniki łączące węzeł z dokładnie dwoma węzłami potomnymi — lewym i prawym.</p>

<p>Trzy poziomy drzewa binarnego pokazano na poniższym rysunku.</p>

<p><img src="/assets/bintree.png" alt="Drzewo binarne"></p>

<p>Za tworzenie drzewa odpowiada rekurencyjna funkcja createTree a następnie liczba węzłów w drzewie jest liczona za pomocą funkcją checkTree, która jednocześnie dokonuje destrukcji drzewa zwalniając wszystkie węzły.</p>

<p>Operacja tworzenia i niszczenia drzewa wykonywana jest wielokrotnie (funkcja loops) z kolejno zwiększającą się wysokością drzewa. Liczba iteracji wyznaczana jest w funkcji trees, która dodatkowo alokuje jedno drzewo o maksymalnej wskazanej głębokości na cały czas testu.</p>

<p>Jak nie trudno się domyślić, benchmark testuje wydajność i efektywność alokacji pamięci na stercie.</p>

<p>Mamy tu dwie kwestie:</p>
<ol><li>Szybkość alokacji wielu małych obiektów</li>
<li>Obsługa zwalniania pamięci.</li></ol>

<p>Taki test jest szczególnie obciążający dla platform wykorzystujących Garbage Collector. Mamy setki tysięcy małych obiektów z referencjami między sobą. To powoduje, że podczas odśmiecania, GC musi przebiec po wszystkich tych obiektach, żeby ustalić, które z nich są żywe (tj. gdzieś w programie jest odwołanie do drzewa obiektów, co oznacza, że należy zachować je przy życiu), a które już nie posiadają odwołań, co oznacza, że pamięć przez nie zajmowana może być oznaczona jako wolna i posłużyć w kolejnych alokacjach.</p>

<p>Wiedząc to wszystko, można spokojnie przyjąć różnica w czasie wykonania programu w Java i Go jednoznacznie wskazuje, że Java ma prawie 6 razy lepszy Garbage Collector niż Go.</p>

<p>I w pewnym sensie tak jest. Java wykorzystuje generacyjny kolektor kompaktujący. To oznacza, że nowe obiekty tworzone są w specjalnym obszarze pamięci (“eden”), która jest zajmowana na zasadzie stosu (tj. istnieje wskaźnik, który mówi gdzie kończy się pamięć zajęta, a zaczyna wolna i każda alokacja kolejnego obiektu polega wyłącznie na przesunięciu tego wskaźnika). Gdy zajęty został cały obszar pamięci dla nowych obiektów, GC przebiega po nich sprawdzając, które są wciąż żywe i kopiuje je do innego obszaru pamięci, wskaźnik ustawiany jest znów na początek i mamy wolną pamięć na kolejne alokacje (bardzo to upraszczam, ale idea jest mniej więcej wyjaśniona).</p>

<p>To oznacza oczywiście, że JVM zmienia adresy zaalokowanych obiektów i trzeba zatrzymać działanie programu i zaktualizować wskaźniki, które na nie wskazują, ale sama alokacja jest faktycznie bardzo szybka.</p>

<p>W Go GC działa inaczej — nie ma generacji, czyli sekcji “nowe obiekty” i “stare obiekty” (Java ma trzy generacje). Raz zaalokowany obiekt nigdy nie zmienia swojego adresu ale alokacja tych obiektów trwa dłużej — trzeba po prostu znaleźć dla nich miejsce na stercie.</p>

<p>Nie ma przesuwania obiektów, nie ma kompaktowania sterty (czyli przesuwania obiektów tak, żeby odzyskać duże obszary wolnej pamięci), ale za to w większym stopniu możliwe jest działania GC równolegle do właściwego programu.</p>

<p>Benchmark wydaje się jednak pokazywać, że rozwiązanie Go się nie sprawdza — podejście Java jest dużo efektywniejsze. Ta wersja mikrobenchmarku Go korzysta nawet z puli obiektów (sync.Pool — obiekty, które nie są już potrzebne, są zwracane do puli, żeby zmniejszyć liczbę alokacji na stercie) i dalej jest wściekle wolna, w porównaniu do Javy.</p>

<p>Co zatem Go robi źle?</p>

<p>Otóż nic — ma zupełnie inne podejście. W Javie praktycznie nie ma innej możliwości niż zaalokowanie obiektu na stercie. Jeśli robię tablicę 100 obiektów typu “MojaKlasa”, to na stercie alokowanych jest 100 niezależnych obiektów oraz sto pierwszy obiekt — sama tablica. Tablica trzyma 100 wskaźników do moich 100 obiektów. Ponieważ każde utworzenie obiektu w Javie musi odbyć się na stercie, GC Javy ma wyrafinowane mechanizmy zapewniające, że te alokacje są bardzo efektywne. Nie dotyczy to wyłącznie typów podstawowych (np. intlub char), które w takiej tablicy zostaną zaalokowane jako ciągły obszar bajtów (referencje technicznie też są zrealizowane jako wskaźniki, które można potraktować jako typy proste, ale Java nie daje żadnego dostępu do tych wskaźników, poza oczywiście referencją do wskazywanego obiektu).</p>

<p>W Go tak nie jest.</p>

<p>Go ma kilka typów referencyjnych (np. chan, slice, map albo string), ale posiada też wskaźniki oraz value types! W Javie tylko zmienne typu int, bool, char etc są wartościami. W Go prawie wszystko jest wartością. Chyba, że potrzebujemy wskaźnika, wtedy wyrażamy to wprost, np var intPtr *int. Ta gwiazdka oznacza, że zmienna intPtr jest wskaźnikiem na wartość int. Zamiast int może być jednak dowolny obiekt — np. nasza struktura Node z powyższego programu.</p>

<p>Kolejna ważna rzecz — w Go można pobrać wskaźnik do wszystkiego co jest adresowalne. A większość obiektów jest: można mieć adres funkcji, adres dowolnej zmiennej, ale też co jest istotne — adres do składowej naszej struktury albo adres do elementu tablicy.</p>

<p>Na przykład:</p>

<pre><code class="language-go">type Moja struct {
  a int
  b int
}
// ....
// &#34;moja&#34; jest zmienną o rozmiarze dwu int-ów i jest to wartość.
// w przykładzie poniżej zostanie zaalokowana na stosie - tj. w ogóle nie trafia na stertę
moja := Moja{
  a: 1,
  b: 2,
}
// dzięki użyciu operatora &amp;, zmienna mojaPtr jest wskaźnikiem do obiektu typu Moja. Obiekt został zaalokowany na stercie.
mojaPtr := &amp;Moja{
  a: 10,
  b: 20,
}
// zmieniam wartość wskaźnika mojaPtr i przypisuję do niego adres obiektu &#34;moja&#34; (tego utworzone wcześniej)
mojaPtr = &amp;moja
// i tu najlepsze mogę tez pobrać adres składowej obiektu:
var intPtr *int
intPtr = &amp;moja.b
// i tablice - tablica 10 obiektów Moja (pojedynczy, ciągły blok 
// pamięci, w którym mieści się 10 obiektów Moja, czyli 20 int-ów
var tab [10]Moja
// mojaPtr wskazuje na ostatni element tablicy
mojaPtr = &amp;tab[9]
</code></pre>

<p>Te cechy języka powodują, że:</p>
<ol><li><p>Nie jest potrzebny specjalny “eden” na stercie, ponieważ krótko żyjące obiekty mogą być z powodzeniem tworzone na stosie, który będzie pełnił taką samą rolę.</p></li>

<li><p>Zaalokowanie tablicy obiektów (nie ważne, czy na stosie, czy na stercie) gwarantuje, że te obiekty są wewnątrz ciągłego bloku pamięci i mogę odwoływać się do nich przez wskaźniki. Co jest fajne — jeśli sekwencyjnie iteruję po obiektach w ciągłym bloku RAM, CPU potrafi dużo efektywniej wykorzystywać cache, niż w przypadku, gdy skaczę chaotycznie po randomowych adresach.</p></li></ol>

<p>3 Znacznie rzadziej występuje potrzeba alokowania obiektów na stercie, a dodatkowo mogę je tam alokować całymi grupami, a nie tylko pojedynczo.</p>

<p>Skoro tak, to czemu nie wykorzystać tych właściwości. Zmieniłem program w taki oto sposób:</p>

<pre><code class="language-go">package main
import (
 &#34;flag&#34;
 &#34;fmt&#34;
 &#34;strconv&#34;
)
type Node struct {
   left, right *Node
}
// --- Istotne zmiany:
var pool = NewArena[Node](1000)
type Arena[N any] struct {
 nodes []N
 chunk int
}
func NewArena[N any](size int) Arena[N] {
 return Arena[N] {
  chunk: size,
  nodes: make([]N, 0, size),
 }
}
func (arena *Arena[N]) New() *N {
    if len(arena.nodes) == 0 {
        arena.nodes = make([]N, arena.chunk)
    }
    n := &amp;(arena.nodes)[len(arena.nodes)-1]
    arena.nodes = (arena.nodes)[:len(arena.nodes)-1]
    return n
}
// -- koniec
const minDepth = 4
func trees(maxDepth int) {
   longLastingNode := createTree(maxDepth)
   depth := 4
   for depth &lt;= maxDepth {
      iterations := 1 &lt;&lt; uint(maxDepth-depth+minDepth) // 16 &lt;&lt; (maxDepth - depth)
   loops(iterations, depth)
      depth += 2
   }
   fmt.Printf(&#34;long lived tree of depth %d\t check: %d\n&#34;, maxDepth,
      checkTree(longLastingNode))
}
func loops(iterations, depth int) {
   check := 0
   item := 0
   for item &lt; iterations {
      t := createTree(depth)
      check += checkTree(t)
      item++
   }
   fmt.Printf(&#34;%d\t trees of depth %d\t check: %d\n&#34;,
      iterations, depth, check)
}
func checkTree(n *Node) int {
   if n.left == nil {
      // parent will sync.Pool.Put
      return 1
   }
   check := checkTree(n.left) + checkTree(n.right) + 1
   n.left = nil
   n.right = nil
   return check
}
func createTree(depth int) *Node {
   node := pool.New()
   if depth &gt; 0 {
      depth--
      node.left = createTree(depth)
      node.right = createTree(depth)
   }
   return node
}
func main() {
   n := 0
   flag.Parse()
   if flag.NArg() &gt; 0 {
      n, _ = strconv.Atoi(flag.Arg(0))
   }
   maxDepth := n
   if minDepth+2 &gt; n {
      maxDepth = minDepth + 2
   }
   {
      stretchDepth := maxDepth + 1
      t := createTree(stretchDepth)
      check := checkTree(t)
      fmt.Printf(&#34;stretch tree of depth %d\t check: %d\n&#34;, stretchDepth, check)
   }
   trees(maxDepth)
}
</code></pre>

<p>Istotne zmiany oznaczyłem komentarzem. Reszta była tylko prostym dostosowaniem kodu do nowego wywołania New() i usunięciem zbędnych w tej wersji wywołań <code>pool.Put</code>.</p>

<p>Nie będę szczegółowo opisywał na czym polega ten kod (używa generyków z go 1.18! :) ) ale istotniejsza jest idea. Nowy obiekt w go można utworzyć np .tak:</p>

<pre><code class="language-go">node := new(Node)
</code></pre>

<p>albo tak:</p>

<pre><code class="language-go">node := &amp;Node{}
</code></pre>

<p>W obu wypadkach w zmiennej node pojawi się wskaźnik do utworzonego na stercie obiektu Node.</p>

<p>Jedno takie wywołanie, to jedna alokacja. Więc robię małą sztuczkę — stosuję własny alokator, który od zaalokuje tablicę 1000 obiektów node i zwróci wskaźnik do jednego z nich.</p>

<p>Moja metoda New zwraca obiekt z zaalokowanej tablicy (jest to wskaźnik na ostatni element tablicy):</p>

<pre><code class="language-go">n := &amp;(arena.nodes)[len(arena.nodes)-1]
return n
</code></pre>

<p>Kolejne tworzenie obiektu nie powoduje już alokacji następnego obiektu ze sterty, tylko zwróci następny element z uprzednio zaalokowanej tablicy. Po 1000 takich wywołań stworzona zostanie kolejna tablica z tysiącem elementów. Efekt tego jest taki, że jest 1000 razy mniej alokacji i 1000 razy mnie obiektów na stercie (choć obiekty są 1000 razy większe).</p>

<p>Jaki będzie tego efekt? Na moim komputerze oryginalny program wykonywał się 21,550 sekund:</p>

<pre><code>time ./binary-trees-6 21
stretch tree of depth 22  check: 8388607
2097152  trees of depth 4  check: 65011712
524288  trees of depth 6  check: 66584576
131072  trees of depth 8  check: 66977792
32768  trees of depth 10  check: 67076096
8192  trees of depth 12  check: 67100672
2048  trees of depth 14  check: 67106816
512  trees of depth 16  check: 67108352
128  trees of depth 18  check: 67108736
32  trees of depth 20  check: 67108832
long lived tree of depth 21  check: 4194303
real 0m21,550s
user 0m22,448s
sys 0m0,156s
</code></pre>

<p>A moja poprawiona wersja:</p>

<pre><code>time ./binary-trees-ar 21
stretch tree of depth 22  check: 8388607
2097152  trees of depth 4  check: 65011712
524288  trees of depth 6  check: 66584576
131072  trees of depth 8  check: 66977792
32768  trees of depth 10  check: 67076096
8192  trees of depth 12  check: 67100672
2048  trees of depth 14  check: 67106816
512  trees of depth 16  check: 67108352
128  trees of depth 18  check: 67108736
32  trees of depth 20  check: 67108832
long lived tree of depth 21  check: 4194303
real 0m4,384s
user 0m8,965s
sys 0m0,142s
</code></pre>

<p>Czyli jest 4,9 razy szybszy!</p>

<p>Niezły wynik. Ale moje rozwiązanie nie jest jakoś szczególnie odkrywcze, więc dlaczego nikt na to nie wpadł i nie przesłał takiej wersji programu?</p>

<p>Otóż wpadłem na to kilka lat temu i wysłałem taką poprawioną wersję (nie było wtedy jeszcze generyków), ale została odrzucona, ponieważ regulamin (czy jak to nazwać) mówi, że nie można stosować dedykowanych/specyficznych alokatorów.</p>

<p>Nie wiem, czy wersja “generyczna” alokatora (tak jak ją przedstawiłem) już spełniałaby kryteria, ale to jest bez znaczenia, bo zrozumiałem wtedy, że celem tych benchmarków jest pokazanie silnych i słabych stron platformy, a nie odpowiedź na pytanie, który język jest “szybszy”.</p>

<p>Z tego można wyciągnąć parę wniosków:</p>
<ol><li>Nie kłóć się o to, który język jest szybszy, podpierając mikrobenchmarkami, bo nie taki jest ich cel i zawsze może się okazać, że ktoś w Pythonie stworzy szybszą implementację danego algorytmu niż Ty w C++ (albo assemblerze — niektórzy do dziś wierzą, że “assembler jest najszybszy”).</li>
<li>Mikrobenchmarki pokazują silne i słabe strony platformy — jak widzisz, że któraś z platform (runtime, maszyna wirtualna) w czymś niedomaga, to oznacza to, że nie należy próbować czegoś w ten sposób używać i należy znaleźć jakieś obejście. Każda platforma ma jakieś słabsze strony.</li>
<li>Nie pisz w Go programu będącego kalką kodu Java (ani C, ani C++). Każdy język ma swoją specyfikę, swoje idiomy. Należy je zrozumieć i wykorzystać wszystko to, co dany język ma najlepszego. Prawdopodobnie niektóre algorytmy mogą być w jakimś języku w ogóle trudne do efektywnej implementacji. Może należy wtedy użyć zupełnie innego algorytmu? Ważne jest osiągnięcie celu — droga do niego może być dowolna.</li></ol>

<p>Tym niemniej przyspieszenie czego ponad 5 razy zawsze cieszy.</p>

<p>Dzięki, jeśli doszedłeś aż do tego miejsca ;)</p>

<p>Jeśli masz jakiś uwagi, napisz na <a href="https://mastodon.social/@ark_r" rel="nofollow"> <a href="https://baal.ar76.eu/@/ark_r@mastodon.social" class="u-url mention" rel="nofollow">@<span>ark_r@mastodon.social</span></a> </a></p>

<p><a href="/arek/tag:go" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">go</span></a> <a href="/arek/tag:golang" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">golang</span></a>
<a href="/arek/tag:optymalizacja" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">optymalizacja</span></a> <a href="/arek/tag:benchmark" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">benchmark</span></a></p>

<p><a href="https://mastodon.social/@ark_r" rel="nofollow">@ark_r</a></p>
]]></content:encoded>
      <guid>https://baal.ar76.eu/arek/o-optymalizacji-i-wydajnosci</guid>
      <pubDate>Mon, 25 Jul 2022 20:55:53 +0000</pubDate>
    </item>
  </channel>
</rss>