1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
|
---
layout: nng
title: Rationale: Or why am I bothering to rewrite nanomsg?
---
<main>
<div id="header">
<h1>Rationale: Or why am I bothering to rewrite nanomsg?</h1>
<div class="details">
<span id="author" class="author">Garrett D’Amore</span><br>
<span id="email" class="email"><a href="mailto:garrett@damore.org">garrett@damore.org</a></span><br>
<span id="revnumber">version 0.4,</span>
<span id="revdate">January 28, 2020</span>
</div>
</div>
<div id="content">
<div id="preamble">
<div class="sectionbody">
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
You might want to review
<a href="http://nanomsg.org/documentation-zeromq.html">Martin Sustrik’s rationale</a>
for nanomsg vs. ZeroMQ.
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_background">Background</h2>
<div class="sectionbody">
<div class="paragraph">
<p>I became involved in the
<a href="http://www.nanomsg.org">nanomsg</a> community back in 2014, when
I wrote <a href="https://github.com/go-mangos/mangos">mangos</a> as a pure
<a href="http://www.golang.org">Go</a> implementation of the wire protocols behind
<em>nanomsg</em>. I did that work because I was dissatisfied with the
<a href="http://zeromq.org"><em>ZeroMQ</em></a> licensing model
and the C++ baggage that came with it. I also needed something that would
work with <em>Go</em> on <a href="http://www.illumos.org">illumos</a>, which at the time
lacked support for <code>cgo</code> (so I could not just use an FFI binding.)</p>
</div>
<div class="paragraph">
<p>At the time, it was the only alternate implementation those protocols.
Writing <em>mangos</em> gave me a lot of detail about the internals of <em>nanomsg</em> and
the SP protocols.</p>
</div>
<div class="paragraph">
<p>It would not be wrong to say that one of the goals of <em>mangos</em> was to teach
me about <em>Go</em>. It was my first non-trivial <em>Go</em> project.</p>
</div>
<div class="paragraph">
<p>While working with <em>mangos</em>, I wound up implementing a number of additional
features, such as a TLS transport, the ability to bind to wild card ports,
and the ability to determine more information about the sender of a message.
This was incredibly useful in a number of projects.</p>
</div>
<div class="paragraph">
<p>I initially looked at <em>nanomsg</em> itself, as I wanted to add a TLS transport
to it, and I needed to make some bug fixes (for protocol bugs for example),
and so forth.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_lessons_learned">Lessons Learned</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Perhaps it might be better to state that there were a number of opportunities
to learn from the lessons of <em>nanomsg</em>, as well as lessons we learned while
building <em>NNG</em> itself.</p>
</div>
<div class="sect2">
<h3 id="_state_machine_madness">State Machine Madness</h3>
<div class="paragraph">
<p>What I ran into in <em>nanomsg</em>, when attempting to improve it, was a
challenging mess of state machines. <em>nanomsg</em> has dozens of state machines,
many of which feed into others, such that tracking flow through the state
machines is incredibly painful.</p>
</div>
<div class="paragraph">
<p>Worse, these state machines are designed to be run from a single worker
thread. This means that a given socket is entirely single threaded; you
could in theory have dozens, hundreds, or even thousands of connections
open, but they would be serviced only by a single thread. (Admittedly
non-blocking I/O is used to let the OS kernel calls run asynchronously
perhaps on multiple cores, but nanomsg itself runs all socket code on
a single worker thread.)</p>
</div>
<div class="paragraph">
<p>There is another problem too — the <code>inproc</code> code that moves messages
between one socket and another was incredibly racy. This is because the
two sockets have different locks, and so dealing with the different
contexts was tricky (and consequently buggy). (I’ve since, I think, fixed
the worst of the bugs here, but only after many hours of pulling out hair.)</p>
</div>
<div class="paragraph">
<p>The state machines also make fairly linear flow really difficult to follow.
For example, there is a state machine to read the header information. This
may come a byte a time, and the state machine has to add the bytes, check
for completion, and possibly change state, even if it is just reading a
single 32-bit word. This is a lot more complex than most programmers are
used to, such as <code>read(fd, &val, 4)</code>.</p>
</div>
<div class="paragraph">
<p>Now to be fair, Martin Sustrik had the best intentions when he created the
state machine model around which <em>nanomsg</em> is built. I do think that from
experience this is one of the most dense and unapproachable parts of <em>nanomsg</em>,
in spite of the fact that Martin’s goal was precisely the opposite. I
consider this a "failed experiment" — but hey failed experiments are the
basis of all great science.</p>
</div>
</div>
<div class="sect2">
<h3 id="_thread_challenges">Thread Challenges</h3>
<div class="paragraph">
<p>While <em>nanomsg</em> is mostly internally single threaded, I decided to try to
emulate the simple architecture of <em>mangos</em> using system threads. (<em>mangos</em>
benefits greatly from <em>Go</em>'s excellent coroutine facility.) Having been well
and truly spoiled by <em>illumos</em> threading (and especially <em>illumos</em> kernel
threads), I thought this would be a reasonable architecture.</p>
</div>
<div class="paragraph">
<p>Sadly, this initial effort, while it worked, scaled incredibly poorly — even so-called "modern" operating systems like <em>macOS</em> 10.12 and <em>Windows</em> 8.1
simply melted or failed entirely when creating any non-trivial number of
threads. (To me, creating 100 threads should be a no-brainer, especially if
one limits the stack size appropriately. I’m used to be able to create
thousands of threads without concern. As I said, I’ve been spoiled.
If your system falls over at a mere 200 threads I consider it a toy
implementation of threading. Unfortunately most of the mainstream operating
systems are therefore toy implementations.)</p>
</div>
<div class="paragraph">
<p>Chalk up another failed experiment.</p>
</div>
<div class="paragraph">
<p>I did find another approach which is discussed further.</p>
</div>
</div>
<div class="sect2">
<h3 id="_file_descriptor_driven">File Descriptor Driven</h3>
<div class="paragraph">
<p>Most of the underlying I/O in <em>nanomsg</em> is built around file descriptors,
and it’s internal usock structure, which is also state machine driven.
This means that implementing new transports which might need something
other than a file descriptor, is really non-trivial. This stymied my
first attempt to add <a href="http://www.openssl.org">OpenSSL</a> support to get TLS
added — <em>OpenSSL</em> has it’s own <code>struct BIO</code> for this stuff, and I could
not see an easy way to convert <em>nanomsg</em>'s <code>usock</code> stuff to accommodate the
<code>struct BIO</code>.</p>
</div>
<div class="paragraph">
<p>In retrospect, <em>OpenSSL</em> wasn’t the ideal choice for an SSL/TLS library,
and we have since chosen another (<a href="https://tls.mbed.org">mbed TLS</a>).
Still, we needed an abstraction model that was better than just file
descriptors for I/O.</p>
</div>
</div>
<div class="sect2">
<h3 id="_poll">Poll</h3>
<div class="paragraph">
<p>In order to support use in event driven programming, asynchronous
situations, etc. <em>nanomsg</em> offers non-blocking I/O. In order to make
this work for end-users, a notification mechanism is required, and
nanomsg, in the spirit of following POSIX, offers a notification method
based on <code>poll(2)</code> or <code>select(2)</code>.</p>
</div>
<div class="paragraph">
<p>In order for this to work, it offers up a selectable file descriptor
for send and another one for receive. When events occur, these are
written to, and the user application "clears" these by reading from
them. (This is done on behalf of the application by <em>nanomsg</em>'s API calls.)</p>
</div>
<div class="paragraph">
<p>This means that in addition to the context switch code, there are not
fewer than 2 extra system calls executed per message sent or received, and
on a mostly idle system as many as 3. This means that to send a message
from one process to another you may have to execute up to 6 extra system
calls, beyond the 2 required to actually send and receive the message.</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
Its even more hideous to support this on Windows, where there is no
<code>pipe(2)</code> system call, so we have to cobble up a loopback TCP connection
just for this event notification, in addition to the system call
explosion.
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>There are cases where this file descriptor logic is easier for existing
applications to integrate into event loops (e.g. they already have a thread
blocked in <code>poll()</code>.)</p>
</div>
<div class="paragraph">
<p>But for many cases this is not necessary. A simple callback mechanism
would be far better, with the FDs available only as an option for code
that needs them. This is the approach that we have taken with <em>NNG</em>.</p>
</div>
<div class="paragraph">
<p>As another consequence of our approach, we do not require file descriptors
for sockets at all, so it is possible to create applications containing
<em>many</em> thousands of <code>inproc</code> sockets with no files open at all. (Obviously
if you’re going to perform real I/O to other processes or other systems,
you’re going to need to have the underlying transport file descriptors
open, but then the only real limit should be the number of files that you
can open on your system. And the number of active connections you can maintain
should ideally approach that system limit closely.)</p>
</div>
</div>
<div class="sect2">
<h3 id="_posix_apis">POSIX APIs</h3>
<div class="paragraph">
<p>Another of Martin’s goals, which seems worthwhile at first, was the
attempt to provide a familiar POSIX API (based upon the BSD socket API).
As a C programmer coming from UNIX systems, this really attracted me.</p>
</div>
<div class="paragraph">
<p>The problem is that the POSIX APIs are actually really horrible. In
particular the semantics around <code>cmsg</code> are about as arcane and painful as
one can imagine. Largely, this has meant that extensions to the `cmsg
API simply have not occurred in <em>nanomsg</em>.</p>
</div>
<div class="paragraph">
<p>The <code>cmsg</code> API specified by POSIX is as bad as it is because POSIX had
requirements not to break APIs that already existed, and they needed to
shim something that would work with existing implementations, including
getting across a system call boundary. <em>nanomsg</em> has never had such
constraints.</p>
</div>
<div class="paragraph">
<p>Oh, and there was that whole "design by committee" aspect.</p>
</div>
<div class="paragraph">
<p>Attempting to retain low numbered "socket descriptors" had its own
problems — a huge source of use-after-close bugs, which made the
use of <code>nn_close()</code> incredibly dangerous for multi-threaded sockets.
(If one thread closes and opens a new socket, other threads still using
the old socket might wind up accessing the "new" socket without realizing
it.)</p>
</div>
<div class="paragraph">
<p>The other thing is that BSD socket APIs are super familiar to UNIX C
programmers — but experience with <em>nanomsg</em> has taught us already that these
are actually in the minority of <em>nanomsg</em>'s users. Most of our users are
coming to us from C++ (object oriented), <em>Java</em>, and <em>Python</em> backgrounds.
For them the BSD sockets API is frankly somewhat bizarre and alien.</p>
</div>
<div class="paragraph">
<p>With <em>NNG</em>, we realized that constraining ourselves to the mistakes of the
POSIX API was hurting rather than helping. So <em>NNG</em> provides a much friendlier
interface for getting properties associated with messages.</p>
</div>
<div class="paragraph">
<p>In <em>NNG</em> we also generally try hard to avoid reusing
an identifier until no other option exists. This generally means most
applications won’t see socket reuse until billions of other sockets
have been opened. There is little chance for accidental reuse.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_compatibility">Compatibility</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Of course, there are a number of existing <em>nanomsg</em> consumers “in the wild”
already. It is important to continue to support them. So I decided from
the get go to implement a “compatibility” layer, that provides the same
API, and as much as possible the same ABI, as legacy <em>nanomsg</em>. However,
new features and capabilities would not necessarily be exposed to the
the legacy API.</p>
</div>
<div class="paragraph">
<p>Today <em>NNG</em> offers this. You can relink an existing <em>nanomsg</em> binary against
<em>libnng</em> instead of <em>libnn</em>, and it usually Just Works™. Source
compatibility is almost as easy, although the application code needs to be
modified to use different header files.</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
I am considering changing the include file in the future so that
it matches exactly the <em>nanomsg</em> include path, so that only a compiler
flag change would be needed.
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_asynchronous_io">Asynchronous IO</h2>
<div class="sectionbody">
<div class="paragraph">
<p>As a consequence of our experience with threads being so un-scalable,
we decided to create a new underlying abstraction modeled largely on
Windows IO completion ports. (As bad as so many of the Windows APIs
are, the IO completion port stuff is actually pretty nice.) Under the
hood in <em>NNG</em> all I/O is asynchronous, and we have <code>nni_aio</code> objects
for each pending I/O. These have an associated completion routine.</p>
</div>
<div class="paragraph">
<p>The completion routines are <em>usually</em> run on a separate worker thread
(we have many such workers; in theory the number should be tuned to the
available number of CPU cores to ensure that we never wait while a CPU
core is available for work), but they can be run "synchronously" if
the I/O provider knows it is safe to do so (for example the completion
is occurring in a context where no locks are held.)</p>
</div>
<div class="paragraph">
<p>The <code>nni_aio</code> structures are accessible to user applications as well, which can
lead to much more efficient and easier to write asynchronous applications,
and can aid integration into event-driven systems and runtimes, without
requiring extra system calls required by the legacy <em>nanomsg</em> approach.</p>
</div>
<div class="paragraph">
<p>There is still performance tuning work to do, especially optimization for
specific pollers like <code>epoll()</code> and <code>kqueue()</code> to address the C10K problem,
but that work is already in progress.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_portability_embeddability">Portability & Embeddability</h2>
<div class="sectionbody">
<div class="paragraph">
<p>A significant goal of <em>NNG</em> is to be portable to many kinds of different
kinds of systems, and embedded in systems that do not support POSIX or Win32
APIs. To that end we have a clear platform portability layer. We do require
that platforms supply entry points for certain networking, synchronization,
threading, and timekeeping functions, but these are fairly straight-forward
to implement on any reasonable 32-bit or 64-bit system, including most
embedded operating systems.</p>
</div>
<div class="paragraph">
<p>Additionally, this portability layer may be used to build other kinds of
experiments — for example it should be relatively straight-forward to provide
a "platform" based on one of the various coroutine libraries such as Martin’s
<a href="http://libdill.org">libdill</a> or <a href="https://swtch.com/libtask/">libtask</a>.</p>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
If you want to write a coroutine-based platform, let me know!
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_new_transports">New Transports</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The other, most critical, motivation behind <em>NNG</em> was to enable an easier
creation of new transports. In particular, one client (
<a href="http://www.capitar.com">Capitar IT Group BV</a>)
contracted the creation of a <a href="http://www.zerotier.com">ZeroTier</a> transport for
<em>nanomsg</em>.</p>
</div>
<div class="paragraph">
<p>After beating my head against the state machines some more, I finally asked
myself if it would not be easier just to rewrite <em>nanomsg</em> using the model
I had created for <em>mangos</em>.</p>
</div>
<div class="paragraph">
<p>In retrospect, I’m not sure that the answer was a clear and definite yes
in favor of <em>NNG</em>, but for the other things I want to do, it has enabled a
lot of new work. The ZeroTier transport was created with a relatively
modest amount of effort, in spite of being based upon a connectionless
transport. I do not believe I could have done this easily in the existing
<em>nanomsg</em>.</p>
</div>
<div class="paragraph">
<p>I’ve since added a rich TLS transport, and have implemented a WebSocket
transport that is far more capable than that in <em>nanomsg</em>, as it can
support TLS and sharing the TCP port across multiple <em>NNG</em> sockets (using
the path to discriminate) or even other HTTP services.</p>
</div>
<div class="paragraph">
<p>There are already plans afoot for other kinds of transports using QUIC
or KCP or SSH, as well as a pure UDP transport. The new <em>NNG</em> transport
layer makes implementation of these all fairly straight-forward.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_http_and_other_services">HTTP and Other services</h2>
<div class="sectionbody">
<div class="paragraph">
<p>As part of implementing a real WebSocket transport, it was necessary to
implement at least some HTTP capabilities. Rather than just settle for a toy
implementation, <em>NNG</em> has a very capable HTTP server and client framework.
The server can be used to build real web services, so it becomes possible
for example to serve static content, REST API, and <em>NNG</em> based services
all from the same TCP port using the same program.</p>
</div>
<div class="paragraph">
<p>We’ve also made the WebSocket services fairly generic, which may support
a plethora of other kinds of transports and services.</p>
</div>
<div class="paragraph">
<p>There is also a portability layer — so some common services (threading,
timing, etc.) are provided in the <em>NNG</em> library to help make writing
portable <em>NNG</em> applications easier.</p>
</div>
<div class="paragraph">
<p>It will not surprise me if developers start finding uses for <em>NNG</em> that
have nothing to do with Scalability Protocols.</p>
</div>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p>Actually, now in 2020, this has come to pass. There exists, for
example, a project that provides a full REST API framework built on top
NNG.</p>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_streaming_api">Streaming API</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Along with the ability to use Scalability Protocols, and a generic
HTTP framework, we’ve provided a nice abstraction for byte-stream
oriented protocols. This makes it possible to build portable applications
that can use WebSockets, TCP, IPC, and TLS all in much the same way.
In 2019, we refactored much of the core Scalability Protocols code to
be based on top of this work.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_separate_contexts">Separate Contexts</h2>
<div class="sectionbody">
<div class="paragraph">
<p>As part of working on a demo suite of applications, I realized that the
requirement to use raw mode sockets for concurrent applications was rather
onerous, forcing application developers to re-implement much of the
same logic that is already in <em>NNG</em>.</p>
</div>
<div class="paragraph">
<p>Thus was the born the idea of separating the context for protocols from
the socket, allowing multiple contexts (each of which managing it’s own
REQ/REP state machinery) to be allocated and used on a single socket.</p>
</div>
<div class="paragraph">
<p>This was a large change indeed, but we believe application developers
are going to find it <strong>much</strong> easier to write scalable applications,
and hopefully the uses of raw mode and applications needing to inspect
or generate their own application headers will vanish.</p>
</div>
<div class="paragraph">
<p>Note that these contexts are entirely optional — an application can
still use the implicit context associated with the socket just like
always, if it has no need for extra concurrency.</p>
</div>
<div class="paragraph">
<p>One side benefit of this work was that we identified several places
to make <em>NNG</em> perform more efficiently, reducing the number of context
switches and extra raw vs. cooked logic.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_towards_nanomsg_2_0">Towards <em>nanomsg</em> 2.0</h2>
<div class="sectionbody">
<div class="paragraph">
<p>It is my intention that <em>NNG</em> ultimately replace <em>nanomsg</em>. I do think of it
as "nanomsg 2.0". In fact “NNG” stands for "nanomsg next generation" in mind.
Some day soon I’m hoping that the various website
references to nanomsg my simply be updated to point at <em>NNG</em>.
It is not clear to me whether at that time I will simply rename the existing
code to <em>nanomsg</em>, nanomsg2, or leave it as <em>NNG</em>.</p>
</div>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p>My thinking in 2020, is that the <em>nanomsg</em> has significant value, but now
<em>NNG</em> has carved an identity out of it’s own, as has mangos.
The legacy <em>nanomsg</em> project is likely to thus be re-branded as <em>libnanomsg</em>,
and we will re-purpose the <em>nanomsg</em> brand to represent the entire family
of projects around these protocols. In fact <em>NNG</em> has already been
relocated to it’s own <a href="https://nng.nanomsg.org">site</a> under
<a href="https://nanomsg.org">nanomsg.org</a>.</p>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_future_directions">Future Directions</h2>
<div class="sectionbody">
<div class="paragraph">
<p>In the few years since I started the <em>NNG</em> project, it’s become apparent
that a few enhancements are sorely needed. A few things we have planned
as of January 2020 are:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>support for pluggable TLS libraries (including FIPS and liberal licensed options)</p>
</li>
<li>
<p>better support for UDP (including multicast when appropriate)</p>
</li>
<li>
<p>new wire protocols to negotiate capabilities between peers</p>
</li>
<li>
<p>publisher side filtered PUB/SUB protocol</p>
</li>
<li>
<p>a true mesh protocol, including directed unicast and peer discovery</p>
</li>
<li>
<p>better support for QoS tuning in certain protocols</p>
</li>
<li>
<p>richer statistics and observability</p>
</li>
<li>
<p>support for HTTP/2</p>
</li>
<li>
<p>more platform ports to platforms like FreeRTOS</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Of course, we will also continue to make strides in improving the performance
and scalability of the library. In the past several months alone there have
been significant moves in this direction, and we’ve identified several other
major opportunities to improve performance and reduce latency.</p>
</div>
<div class="paragraph">
<p>Also, we’ve been working to improve our test coverage, which has uncovered
a raft of bugs (and we expect will continue to help us find and squash them),
as well as identifying some areas to tighten the code leading to both
simpler, and more efficient implementation.</p>
</div>
<div class="paragraph">
<p>The future of NNG is looking bright, indeed.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_history">History</h2>
<div class="sectionbody">
<table class="tableblock frame-all grid-all stretch">
<colgroup>
<col style="width: 33.3333%;">
<col style="width: 33.3333%;">
<col style="width: 33.3334%;">
</colgroup>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">v0.4</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">January 28, 2020</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Branding, updates for the new decade.</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">v0.3</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">April 12, 2018</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Minor fixes, added contexts.</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">v0.2</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">February 23, 2018</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Updates with new information, markup fixes.</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">v0.1</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">October 18, 2017</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">First version.</p></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</main>
|