diff --git a/TODO.txt b/TODO.txt index 080f850..c67275c 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,13 +1,12 @@ Urgent: -- [ ] Images sam -- [ ] Finir la plannif +- [*] Finir la plannif - [ ] MVP -- [ ] Epoll (ptetre lier ca a BD ?) -- [ ] Changer le _index.org qui fait qu on arrive sur Utils N importe quand: +- [ ] Changer le _index.org qui fait qu on arrive sur Utils +- [ ] Epoll (ptetre lier ca a BD ?) - [ ] error.CeQueJeVeux -- [ ] Corriger le feedback web et concu +- [*] Corriger le feedback web et concu - [ ] tout relier et mettre Dans mes conclusion mettre des liens vers ma doc - [ ] Essayer export pdf avec les nouveaux *** - [ ] Noter tous les documents checkes et pas checks @@ -15,9 +14,11 @@ N importe quand: - [ ] Verifier que tous les premieres scripts sont exectuables - [ ] Un script pour lancer les serveurs se serait pas mal - [ ] https://zig.news/minosian/deciphering-many-item-types-the-skeleton-19i +- [ ] Faire joli Readme Fin: -- [ ] sources zotero + - [ ] sources zotero +- [ ] Changer les titres, les images et les liens Peut etre: - [?] Websocket @@ -26,6 +27,18 @@ Peut etre: - [?] debug, binutils, breakpoints, debugger Questions: +- Feedback bien dans l'ensemble ? J avais l impression conclusion un peu maigre. Structure bien aussi psq surtout remarques legeres. - Moyen que l expert regarde le site plutot que le document PDF pour la doc ? +- Jdois faire quoi en + du rapport ? Une affiche et cest tout ? +Remarques sur feedback: +- Preemptimve multitasking: "useful" le dernier bout de taf peut etre useful pour moi pareil pour "is short lived" cest pas forcement vrai, ca peut etre une groooose tache +- Kernel threads: "it seem a repetition" +- Async/await: "officials" pour moi cest ok car cest les officiels +- Async/await: "intro to libraries??" +- Pas sur de mon graphe sur les coroutines +- std.Thread: "you cannot conclude much" +- est ce que mon lien pour prouver que allocator pour Wasi est suffisant ? +- stack_size: "standard zig stack" cest bien ca +- conclusion : "leaky ??" ?? diff --git a/content/docs/concurrency/_index.org b/content/docs/concurrency/_index.org index 603e541..e1210cf 100644 --- a/content/docs/concurrency/_index.org +++ b/content/docs/concurrency/_index.org @@ -1,24 +1,21 @@ #+title: Concurrency -#+weight: 30 +#+weight: 15 #+hugo_cascade_type: docs #+math: true ** Introduction The objectives of this chapter is to go in depth about the different ways to do concurrency in ZIG. -We are first going to explore a few definitions and concepts that are going to be useful to understand the rest of the chapter. - -Then we are going to explore the different ways we could achieve concurrency in Zig. - +We are first going to explore a few definitions and concepts that are going to be useful to understand the rest of the chapter. Then we are going to explore the different ways we could achieve concurrency in Zig. By the end you should be able to see the pros and cons of each solution and choose the one that fits your needs to develop your next Zig projects. ** Definitions -Before diving into the different ways to do concurrency in ZIG, let's first define some terms that are useful to understand the basics of concurrency (not related to Zig). Some terms we are going to explain here might not even be needed in the next sections, but might help you clarify your concurency concepts. +Before diving into the different ways to do concurrency in ZIG, let's first define some terms that are useful to understand the basics of concurrency that are not necessarly related to Zig. By the end of reading the definitions you should have a better understanding about the implementations we are going to see later. -It is important to note that the borders between some definitions are really blur and it is possible that you read a slightly different definition in another source. +It is important to note that the boundaries between some definitions are really blur and it is possible that you read a slightly different definition from an other source. *** Coroutine -Courtines enable the management of concurrent tasks like callbacks do (which make them not concurrent by nature but they are a tool to achieve concurrency). Their great power lies in their ability to write concurent task like you would write sequential code. They achieve this by yielding control between them. They are used for cooperative multitasking, since the control flow is managed by themselves and not the OS. You might see them in a lot of languages like Python, Lua, Kotlin, ... with keywords like **yield**, **resume**, **suspend**, ... +Courtines enable the management of concurrent tasks like callbacks do (which make them not concurrent by nature but they are a tool to achieve concurrency). Their great power lies in their ability to write concurent tasks like you would write sequential code. They achieve this by yielding control between them. They are used for cooperative multitasking, since the control flow is managed by themselves and not the OS. You might see them in a lot of languages like [[https://docs.python.org/3/library/asyncio-task.html][Python]], [[https://www.lua.org/pil/9.1.html][Lua]], [[https://kotlinlang.org/docs/coroutines-overview.html][Kotlin]] with keywords like **yield**, **resume** and **suspend**. Coroutines can be either stackful or stackless, we are not gonna dive deep into this concept since most of the time you are going to use stackful coroutines since they allow you to suspend from within a nested stackframe (the only strength of stackless coroutines: efficiency) @@ -30,7 +27,13 @@ The only way to transfer the control flow is by explicitly passing control **to **** Asymmetric coroutines (called asymmetric because the control-transfer can go both ways) - They have two control-transfer mechanisms: 1. invoking another coroutine which is going to be the subcoroutine of the calling coroutine -2. suspending itself and giving control back to the caller +2. suspending itself and giving control back to the caller, meaning that the coroutine does not have to specifiy to which coroutine it is going to give the control back. + +#+CAPTION: Asymmetric coroutines +#+NAME: fig:SED-HR4049 +[[/HEIG_ZIG/images/coroutines.png]] + +You can get more infromations about those 2 last concepts by reading this [[https://stackoverflow.com/questions/41891989/what-is-the-difference-between-asymmetric-and-symmetric-coroutines][StackOverflow thread]]. *** Green threads (userland threads) Green threads, also known as userland threads are managed by a runtime or VM (userspace either way) instead of the OS scheduler (kernel space) that manages standard kernel threads. @@ -45,7 +48,7 @@ Be careful when using green threads not to use blocking functions, because block *** Preemptive multitasking In preemptive multitasking it is the underlying architecture (not us, but the OS or the runtime for exemple) that is in charge of choosing which threads to execute and when. This implies that our threads can be stopped (preempted) at any time, even if it is in the middle of a task. -This method gives the advantage of not having to worry about a thread being starved, since the underlying architecture is going to make sure that everyone gets enough CPU time. As we can see in the 2 graphs for preemptive and cooperative multitasking, the preemptive multitasking might context switch our tasks too much, which can lead to a lot of overhead. +This method gives the advantage of not having to worry about a thread being starved, since the underlying architecture is going to make sure that everyone gets enough CPU time. As we can see in the 2 graphs for preemptive and cooperative multitasking, the preemptive multitasking might context switch our tasks too much, which can lead to a lot of undersiable overhead. There is an interesting thing that is happening in this graph, at the end we see that task 1 is only doing a very small job, which cost a context switch for almost no job, but the scheduler does not know that a task remains only a small job until done and can context switch it. #+CAPTION: Preemptive multitasking @@ -63,7 +66,7 @@ In cooperative multitasking, we can yield the control back whenever we want, whi [[/images/coop.svg]] *** Kernel threads -Multithreading, it is the most basic and history way to do concurrency, it works by running the work on multiple threads that are going to be exectued in parallel (if the CPU can), each thread runs independently of the others. Unlike asynchronous event-driven programming, threads typically block until their assigned task completes. +Multithreading, it is the most basic and historical way to do concurrency, it works by running the work on multiple threads that are going to be exectued in parallel (if the CPU can), each thread runs independently of the others. Unlike asynchronous event-driven programming, threads typically block until their assigned task completes. Threads are managed by the OS scheduler which is going to decide when to execute which thread. @@ -74,7 +77,7 @@ However, scalability can become a concern when managing numerous threads. The ov To avoid this overhead, thread pools are often used, which manage a set of threads that can be reused for multiple tasks. This approach reduces the overhead of creating and destroying threads for each task, making it more efficient and scalable. *** Event-driven programming -Event-driven programming, is basically an event loop that listen for "events". This architecture. Under the hood this works by having an event loop that is going to poll for events and check regulary if an event has been emitted. Those events can be for exemple interupts or signals. +Event-driven programming, is basically an event loop that listen for "events". Under the hood this works by having an event loop that is going to poll for events and check regulary if an event has been emitted. Those events can be for exemple interupts or signals. *** Asynchronous programming (non-blocking IO) Asynchronous IO can be achieved by opening non-blocking sockets and the by using one of those 2 methods: diff --git a/content/docs/concurrency/conclusion.org b/content/docs/concurrency/conclusion.org index 5c9cdc4..a9582fa 100644 --- a/content/docs/concurrency/conclusion.org +++ b/content/docs/concurrency/conclusion.org @@ -2,11 +2,11 @@ #+weight: 100 #+hugo_cascade_type: docs -Zig is a really new language which is not very mature yet, that is why you mind find only a few ressources online about the state of concurrency in Zig. However there are already a few pioneers who have really intersting projects or talks. The first that greatly helped understand concurency especially in Zig is [[https://x.com/kingprotty?lang=en][King Protty]] who answered a few of my questions on the Zig Discord server and also made 2 nice videos [[https://www.youtube.com/watch?v=8k33ZvWYQ20][1]] [[https://www.youtube.com/watch?v=Ul8OO4vQMTw][2]]. The big project that uses concurency in Zig is [[https://tigerbeetle.com/][TigerBeetle]] who designed a highly efficient database engine using. +Zig is a really new language which is not very mature yet, that is why you mind find only a few ressources online about the state of concurrency in Zig. However there are already a few pioneers who have really intersting projects or talks. The first that greatly helped understand concurency especially in Zig is [[https://x.com/kingprotty?lang=en][King Protty]] who answered a few of my questions on the Zig Discord server and also made 2 nice videos [[https://www.youtube.com/watch?v=8k33ZvWYQ20][1]] [[https://www.youtube.com/watch?v=Ul8OO4vQMTw][2]]. The big project that uses concurency in Zig is [[https://tigerbeetle.com/][TigerBeetle]] whish is an highly efficient database engine using. The normal way to do async IO in Zig would be to use its [[file:./async_await][async/await]]async/await feature, but since it is not supported anymore it is completly out of the picture for now. If you still find it interesting to work this way then [[file:./zigcoro][Zigocoro]] might be the best fit since it is almost the interface so when async/await will come back into the language it will be an easy migration, except if the async/await of the language release breaking changes. -The most traditional and easiest way to deal with asynchronous code would be to use [[file:./std.Thread][Kernal threads]] that are disposable in standard library making it easy to use without having to import any external library. The basic functionnalites are very equivalent to those of [[https://man7.org/linux/man-pages/man7/pthreads.7.html][POSIX threads]] making it easy to understand for any C developer. +The most traditional and easiest way to deal with asynchronous code would be to use [[file:./std.Thread][Kernel threads]] that are available in standard library making it easy to use without having to import any external library. The basic functionnalites are very equivalent to those of [[https://man7.org/linux/man-pages/man7/pthreads.7.html][POSIX threads]] making it easy to understand for any C developer. If you want to monitor non-blocking IO operations without spawning kernel threads which have a big overhead, [[file:./libxev_libuv][libuv]] is the event loop that you should use and [[file:./epoll][epoll]] if you prefer a lower abstraction level and only need to work linux. diff --git a/content/docs/concurrency/std.Thread.org b/content/docs/concurrency/std.Thread.org index e571819..4e536ee 100644 --- a/content/docs/concurrency/std.Thread.org +++ b/content/docs/concurrency/std.Thread.org @@ -27,7 +27,7 @@ std.debug.print("Total CPU cores = {!}\n", .{std.Thread.getCpuCount()}); #+end_src **** Thread pool -You could also use a thread pool in order to have a few threads to multiple jobs and not 1 thread = 1 job +You could also use a thread pool in order to have a few threads to multiple jobs and not a thread per job. #+begin_src zig :imports '(std) :main 'yes :testsuite 'no pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; @@ -55,9 +55,9 @@ You could also use a thread pool in order to have a few threads to multiple jobs **** Implementation in the std Under the hood the threads are either pthread ([[https://ziglang.org/documentation/master/std/#std.Thread.use_pthreads][if we are under linux AND linking libc]]) or it is simpy going to use native OS threads (syscalls) wrapped by a Zig implementation. -The advantage of doing multi-threading in Zig is that you don't have to worry about what is the target system going to be, since **std.Thread** implementation automatically chooses the native OS threads for the system your are compiling for (except if you want to enforce the use of pthreads). +The advantage of doing multi-threading in Zig is that you don't have to worry about what the target system is going to be, since **std.Thread** implementation automatically chooses the native OS threads for the system your are compiling for, except if you want to enforce the use of pthreads by linking the libc. -In C if you are using Windows for exemple, since **pthreads** it is not natively supported you would have to use a third-party implementation by adding a compilation tag like so: +In C if you are using Windows for exemple, since **pthreads** it is not natively supported you would have to use a third-party implementation by adding a compilation flag like so: #+begin_src c gcc program.c -o program -pthread #+end_src @@ -171,7 +171,7 @@ Or you could write your own wrapper kind of like the way Zig does (this is not g ***** Zig pthreads vs LinuxThreadImpl vs C pthreads When compiling on Linux, by default your threads are going to use the [[https://github.com/ziglang/zig/blob/28476a5ee94d311319941b54e9da66210690ce70/lib/std/Thread.zig#L1042][LinuxThreadImpl]]. Which under the hood simply is a wrapper around some syscalls in order to manage threads (the code does closely the same thing as the pthread code). -You might have notice that when you are linking libc, Zig is going to use pthreads instead of the LinuxThreadImpl. This is because pthreads are more performant at the moment and since you are already linking libc it is better to take advantage of that and ue pt hreads. +You might have notice that when you are linking libc, Zig is going to use pthreads instead of the LinuxThreadImpl. This is because pthreads are more performant at the moment and since you are already linking libc it is better to take advantage of that and ue pthreads. In order to verify that we are going to benchmarks 3 different implementations: one in Zig using LinuxThreadImpl, one in Zig using pthreads and one in C using pthreads. @@ -199,12 +199,14 @@ Note that it is hard to benchmark thread implementations and you can easily end fn goTo() void {} #+end_src -If we run this code with hyperfine (100 runs) once while linking libc (using pthreads),once in vanilla mode (using LinuxThreadImpl) and a list time using pthreads with C, we can sometimes see that there is indeed a slight performance difference between the them: +If we run this code with hyperfine (100 runs) once while linking libc (using pthreads),once in vanilla mode (using LinuxThreadImpl) and a list time using pthreads with C, we can see that they overlap in the measure variablity, so we can conclude that the performances probably will not change between the different implementations: - Zig pthreads = 274.4 ms += 4.7 ms - LinuxThreadImpl = 276.7s ms += 33.9 ms - C pthreads = 272.7 ms += 34.0 ms -Those test have been run multiple times on different days and the results can vary a bit, but all the implementations can beat each others from time to time, since it is heavily dependent on the OS scheduler and not themselves. +I tried having 100'000 threads instead of 10'000 but I ran out of memory. + +Those test have been run multiple times on different days and the results can vary a bit, but all the implementations can beat each others from time to time, since it is heavily dependent on the OS scheduler and not the thread implementations themselves. The difference is so small that even when only spawning and destroying threads we barely see it. In a real world application where this would very unlikely be the bottleneck, which thread implementation you are going to use is very likely to not change anything the way your program perform. @@ -293,7 +295,7 @@ And the equivalent C code: **** Leaky abstraction There are 2 things you can tweak when using *std.Thread*: the stack size and the allocator. -The allocator you pass is only going to be needed only if you use the [[https://ziglang.org/documentation/master/std/#std.Thread.WasiThreadImpl][WasiThreadImpl]] (which is the default implementation for WebAssembly). +The allocator you pass is only going to be needed if you use the [[https://ziglang.org/documentation/master/std/#std.Thread.WasiThreadImpl][WasiThreadImpl]] (which is the default implementation for WebAssembly). The reasons for that is that it needs the allocator to dynamically allocate the eventuals metadata that are going to be used by the thread. Here is the [[https://github.com/ziglang/zig/blob/1824bee579fffad3f17b639ebb1a94fd890ad68d/lib/std/Thread.zig#L918][code where you can see that]]. #+begin_src zig fn spawn(config: std.Thread.SpawnConfig, comptime f: anytype, args: anytype) SpawnError!WasiThreadImpl { if (config.allocator == null) { @@ -311,7 +313,7 @@ You wont't have to free it anyway since it is only used to be copied like we can allocator.free(self.thread.memory); #+end_src -However, configuring the stack size is going to be used for every implementation of the threads. This is the default stack size: +However, configuring the stack size is going to be used for every implementation of the threads. This is the [[https://github.com/ziglang/zig/blob/1824bee579fffad3f17b639ebb1a94fd890ad68d/lib/std/Thread.zig#L298][default stack size]]: #+begin_src zig /// Size in bytes of the Thread's stack stack_size: usize = 16 * 1024 * 1024 diff --git a/content/docs/concurrency/zigcoro.org b/content/docs/concurrency/zigcoro.org index 1cbeacf..43de999 100644 --- a/content/docs/concurrency/zigcoro.org +++ b/content/docs/concurrency/zigcoro.org @@ -6,12 +6,13 @@ [[https://github.com/rsepassi/zigcoro][This solution]] uses stackful asymmetric coroutines. This library is made to provide similar functionalities to async/await "old" model, so that if/when the official async/await solution is coming back, it will be easy to switch your project from using zigcoro to the official async/await. Under the hood this library uses the [[https://github.com/mitchellh/libxev][libxev]] library that we talked about earlier in order to have an event loop. -But that is not the only features that zigcoro provide, it also provides chanels. -Chanels are notably well known in Go, they are used to communicate between coroutines, they are a way pass data between them, it therefore is a good way to synchronize them. +But that is not the only features that zigcoro provides, it also provides chanels. +Chanels are notably well known in Go, they are used to communicate between coroutines, they are a way pass data to between coroutines. -We are going to do the same thing in Zig in order to communicate between threads without basics thread synchronization primivites like mutexes or semaphores. +**** Example +Here I made a simple example where I simply use chanels to communicate between asynchronous funtions. +I used an analogy to help understand better the code. A client wants its burder order to come to his house, for that the we create the road between the restaurant and the house =road_between_restaurant_and_house=, the delivery man then knows he has to do his job and that he will transmit the order through that road. The client then waits and listens for his order to arrive. Once the delivery man finished the job the order can be consumed by the =hungry_client=. -First we are going to run all of them in a single-threaded environment, so that we can familiarize with the syntax. #+begin_src zig const std = @import("std"); const libcoro = @import("libcoro"); @@ -26,7 +27,7 @@ First we are going to run all of them in a single-threaded environment, so that var exec = libcoro.Executor.init(); libcoro.initEnv(.{ .stack_allocator = allocator, .executor = &exec }); - // Creation of a Type that represents a channel that can passe floats + // Creation of a Type that represents a channel that can passe Burger Orders const BurgeOrderChanel = libcoro.Channel(BurgerOrder, .{}); // Creation of a channel that can pass Burger Orders @@ -38,11 +39,6 @@ First we are going to run all of them in a single-threaded environment, so that const hungry_client = try libcoro.xasync(recvr, .{&road_between_restaurant_and_house}, null); defer hungry_client.deinit(); - while (exec.tick()) { - // While there are deliveries to do, they will be made - // After that point, the delivery man does not take any more orders - } - libcoro.xawait(delivery_man); // Delivery man finished his job const order = libcoro.xawait(hungry_client); // Hungry client received his order std.debug.print("Burger = {} | Fries = {}", .{ order.burger, order.fries }); @@ -58,7 +54,7 @@ First we are going to run all of them in a single-threaded environment, so that } #+end_src -Note that chanels are made for inter coroutine communication only. I tried to use them for inter thread communication but it can not work as states in this [[https://github.com/rsepassi/zigcoro/issues/22][issue]]. +Note that chanels are made for inter coroutine communication only. I tried to use them for inter thread communication but it can not work as stated in this [[https://github.com/rsepassi/zigcoro/issues/22][issue]]. Zigcoro is only maintained by 2 people, even though they still update frequently for the new zig versions, the library has not evolved for a while and there are some PR that are just hanging there for a while. diff --git a/content/docs/project-1/_index.org b/content/docs/project-1/_index.org index 47a0d50..fdbe924 100644 --- a/content/docs/project-1/_index.org +++ b/content/docs/project-1/_index.org @@ -24,7 +24,7 @@ The overall architecture of the game will be the following, here we have an exam #+CAPTION: Requests per second for different frameworks #+NAME: fig:SED-HR4049 -[[/images/architecture.svg]] +[[/HEIG_ZIG/images/architecture.svg]] Since I will be using authentication and that some data might me sensible it is important to use HTTPS, since [[https://github.com/ziglang/zig/issues/17446][Zig does not support TLS]] I am going to use a reverse proxy like Nginx to handle the HTTPS. Since the NGINX and HTTP server are both going to be on the same machine, the security risks are minimal when both communicate in a non-encrypted manner. @@ -32,7 +32,7 @@ Since the NGINX and HTTP server are both going to be on the same machine, the se Having that reverse proxy is mostly useful for security reasons and have no particular link with Zig so I will only do it if I have enough time. #+CAPTION: Requests per second for different frameworks #+NAME: fig:SED-HR4049 -[[/images/https.svg]] +[[/HEIG_ZIG/images/https.svg]] **** Frontend For the client application there is minimum one requirment being that the client has to be able to send and recevie HTTP requests. If I am using anything Zig related this should not be an issue since there is an [[https://ziglang.org/documentation/master/std/#std.http.Client][HTTP client]] in the standard library. diff --git a/content/docs/web/_index.org b/content/docs/web/_index.org index 7dec0d9..5f143e6 100644 --- a/content/docs/web/_index.org +++ b/content/docs/web/_index.org @@ -1,5 +1,5 @@ #+title: Web -#+weight: 15 +#+weight: 16 #+hugo_cascade_type: docs #+math: true diff --git a/static/HEIG_ZIG/images/coroutines.png b/static/HEIG_ZIG/images/coroutines.png new file mode 100644 index 0000000..7119463 Binary files /dev/null and b/static/HEIG_ZIG/images/coroutines.png differ diff --git a/static/images/coroutines.png b/static/images/coroutines.png new file mode 100644 index 0000000..7119463 Binary files /dev/null and b/static/images/coroutines.png differ