-
`std::ref` and `std::reference_wrapper` in C++
In refactoring legacy C++ codebases we often have to deal with a lot of functions or class methods that takes a pointer as an argument and then does a bunch of null checks. This is a common pattern in C++ codebases that are not modernized yet.
Modern C++ has introduced a few utilities to help with this pattern. One of them is
std::ref
andstd::reference_wrapper
. In this post, I wanted to talk about these tools and how they can improve the safety and readability of modern C++ code.
-
Let's build an asyncio runtime from scratch in Python
asyncio
in Python is a library that provides a way to write concurrent code using theasync
andawait
syntax. It is built on top of theasyncio
event loop, which is a single-threaded event loop that runs tasks concurrently. Inspired by a similar post by Jacob, we will explore howasyncio
works from scratch by implementing our own event loop runtime with Python generators.
-
jthread in C++20
std::jthread
introduced in C++20 is a new thread class that is cancellable and joinable. It is a wrapper aroundstd::thread
that provides a few additional features. In this post, I wanted to talk aboutstd::jthread
and how it can be used in modern C++ codebases.Advantages over C++11
std::thread
:- cancellable, can be stopped at any time, unlike
std::thread
which can only be stopped at the end of the thread function - works better with
RAII
pattern, since it can be joined or detached in the destructor
- cancellable, can be stopped at any time, unlike
-
Build a strong type system via Python typehints
Python typehinting system is getting more powerful by each Python version. Projects I’m involved with are now enforcing typehints on all new code. This has been great for a variety of reasons:
- Improves IDE support in terms of linting, autocompletion, and refactoring
- Makes the codebase more readable and maintainable
- Helps catch bugs early in the development cycle
In this post, I’ll share some of the additional features we’ve been able to enable now that most of our codebases are typehinted.
-
Get the Python GIL play nice with C++
It is no surprise that the GIL is one of the biggest drawbacks of using Python in performance oriented applications. The GIL, or Global Interpreter Lock, is a mutex that protects access to Python objects, preventing multiple threads from executing Python bytecodes at once. This means that even if you have multiple threads running in parallel, only one of them can execute Python code at a time. This can be a major bottleneck for applications that require high performance, as it limits the amount of parallelism that can be achieved.
To defeat the GIL, there are two commonly taken path:
- the first is to opt for multiprocessing instead of threads.
- Re-write the core performance critical code using a lower level language such as C++ or Rust
Today, let’s talk about the 2nd approach. With excellent next generation binding libraries such as
pybind11
andpyo3
, it has become a lot simpler to support Rust/C++ code in a Python project.However, often the porting to C++ / Rust from existing application code do not happen overnight. In the beginning, it is mostly a few performance critical functions that are ported to C++ / Rust. In such cases, it is common to see a mix of Python and C++ / Rust code in the same project. In these cases, the threading architecture / parallelism code could still be in Python, while the performance critical code is in C++ / Rust.
I’ve personally dealt with such systems where the GIL became a major bottleneck in the performance of the system due to ill-undertsanding of how it worked. As a result, I’m sharing my findings here.
-
Stack optimization for small sized objects in modern C++
I came across a popular technique for providing a handle for storing small objects in the handle itself and larger ones on the heap. Using modern C++, this can be implemented quite nicely at compile time. Here is a simple example:
// max bytes to store on the stack constexpr int on_stack_max = 20; template<typename T> struct Scoped { // store a T in Scoped // ... T obj; }; template<typename T> struct OnHeap { // store a T on the free store // ... T* objp; }; template<typename T> using Handle = typename std::conditional<(sizeof(T) <= on_stack_max), Scoped<T>, // first alternative OnHeap<T> // second alternative >::type; void f() { Handle<double> v1; // the double goes on the stack Handle<std::array<double, 200>> v2; // the array goes on the free store }
Let’s break this down
constexpr int on_stack_max = 20;
: This line defines a constant expression for the maximum number of bytes that can be stored on the stack.template<typename T> struct Scoped { T obj; };
: This is a template struct that can store an object of any type T on the stack.template<typename T> struct OnHeap { T* objp; };
: This is a template struct that can store a pointer to an object of any type T on the heap.template<typename T> using Handle = typename std::conditional<(sizeof(T) <= on_stack_max), Scoped<T>, OnHeap<T>>::type;
: This line defines a template alias Handle that usesstd::conditional
to decide whether to useScoped<T>
orOn_heap<T>
. If the size ofT
is less than or equal toon_stack_max
, it usesScoped<T>
. Otherwise, it usesOn_heap<T>
.void f() { Handle<double> v1; Handle<std::array<double, 200>> v2; }
: This function demonstrates how to use the Handle template.v1
is a Handle that stores a double on the stack, because the size of a double is less thanon_stack_max
.v2
is a Handle that stores anstd::array<double, 200>
on the heap, because the size ofstd::array<double, 200>
is greater thanon_stack_max
.
Of course, this assumes that
T
can be copied and moved around, and that it has a finite size. IfT
is not copyable or movable, you will need to adjust the implementation accordingly.This shows how powerful modern C++ can be in terms of compile-time programming. It allows you to make decisions at compile time based on the properties of types, which can lead to more efficient and flexible code.
-
Dive into Python asyncio - part 2
In the second part of this series on deep diving into
asyncio
andasync/await
in Python, we will be looking at the following topics:- task, task groups, task cancellation
- async queues
- async locks and semaphores
- async context managers
- async error handling
-
Dive into Python asyncio - part 1
For as long as I have worked in Python land, I never had to touch the async part of the language. I know that
asyncio
library has gotten a lot of love in the past few years. Recently I’ve came across an opportunity to do a lot of IO and non-cpu bound work in Python. I decided to take a deep dive into theasyncio
library and see what it has to offer.In part 1 of this series (I originally just wanted to write one post and realized the scope is way too big), we’ll cover:
- How async code interfaces with synchronous code in Python
- How to convert synchronous code to asynchronous code, including how to prevent blocking of the event loop via custom
ThreadPoolExecutor
- How to use
asyncio
to run multiple tasks concurrently
Basic example, async hello world
import asyncio async def hello_world(): asyncio.sleep(1) print("Hello world") asyncio.run(hello_world()) >>> Hello world
Running two async functions in parallel
import asyncio async def foo(): while True: asyncio.sleep(1) print("foo") async def bar(): while True: asyncio.sleep(1) print("bar") asyncio.run(asyncio.gather(foo(), bar()))
What if I have existing synchronous methods?
We can wrap a synchronous function in an async function, an example implementation would be a decorator (i love decorators, btw):
def async_wrap( loop: Optional[asyncio.BaseEventLoop] = None, executor: Optional[Executor] = None ) -> Callable: def _async_wrap(func: Callable) -> Callable: @wraps(func) async def run(*args, loop=loop, executor=executor, **kwargs): if loop is None: loop = asyncio.get_event_loop() pfunc = partial(func, *args, **kwargs) return await loop.run_in_executor(executor, pfunc) return run return _async_wrap
The above decorator is a higher order decorator (it takes arguments and then generates another decorator), example usage is the following:
import asyncio import time @async_wrap() def foo(): while True: time.sleep(1) print("foo from sync") async def bar(): while True: asyncio.sleep(1) print("bar from async") asyncio.run(asyncio.gather(foo(), bar()))
-
What is copiable?
What is copiable anyway?
Python is garbage collected and has a reference counting system. This means that when you create an object, it is stored in memory and a reference to it is stored in a variable. When you assign a variable to another variable, the reference count for the object is incremented. When you delete a variable, the reference count is decremented. When the reference count reaches zero, the object is deleted from memory.
This is a very simple explanation of how Python works. There are many more details that I will not go into here. The point is that when you assign a variable to another variable, you are not creating a copy of the object. You are creating a new reference to the same object. This is important to understand because it can lead to some unexpected behavior.
Questions I had:
- What happens when you assign a variable to another variable?
- What happens when you return a complex object (i.e. a class) as part of a tuple from a function?
- What happens when you spin up a subprocess, call a method you defined in one class, and give it an object as an argument?
-
Dependency injection in Python
Since Python type hints are introduced, they have made complex Python code-bases much more readable and easier to maintain - especially combined with newer static analysis tools such as
mypy
orpylint
. However, even with these tools, Python is still a dynamic language. When using a dynamic language on a larger application (>5k LOC), the ability to do whatever we wanted any where and any time is more of a curse than a blessing.In this post, I wanted to discuss several options of implementing loosely coupled code in large Python codebases that I have played around with and the final solution of dependency injection based pattern I ended up deciding on.
-
Javascript oddities
A collection of weird things in Javascript:
1.
var
scoping rulesfor (var i = 0; i < 3; ++i) { const log = () => { console.log(`a ${i}`); } setTimeout(log, 100); } for (let i = 0; i < 3; ++i) { const log = () => { console.log(`b ${i}`); } setTimeout(log, 100); }
The output here is:
"a 3" "a 3" "a 3" "b 0" "b 1" "b 2"
Why does
var
cause it to print 3?2.
const
in Javascript does not mean the same as C/C++. Example:const value = 3; value = 4; // error, cannot override a constant value += 3; // error const obj = {a : 3}; obj.a += 3; //allowed obj.a = 5; //allowed
Turns out
const
in Javascript is more of a “const” reference likeconst &
in C++. It does not mean the value itself is constant - just the reference to the array cannot be changed.3. Converting time formats can be tricky
Suppose you have a time in
yyyy-mm-DD
format and you want it inmm/DD/yyyy
format.new Date('2016-06-05'). toLocaleString('en-us', {year: 'numeric', month: '2-digit', day: '2-digit'}) // Output: >>> '06/04/2016'
Wait, what happened?, I asked for 2016-06-05 in
mm/dd/YYYY
but it gave me06/04/2016
instead! This because all dates by default assumes it’s GMT time, when you convert it to a local timezone, you might get a different date.The
moment
library fortunately makes this a lot easier.var date = new Date('10/01/2021'); var formattedDate = moment(date).format('YYYY-MM-DD');
If we don’t want some extra dependency, it’s probably easier to just not convert the date into a Javascript
Date
obj and directly do string operations on it to get it to the format you want. Example:function reformatDateString(dateString) { //reformat date string to from YYYY-MM-DD to MM/DD/YYYY if (dateString && dateString.indexOf('-') > -1) { const dateParts = dateString.split('-'); return `${dateParts[1]}/${dateParts[2]}/${dateParts[0]}`; } return dateString; }
-
Rust like enums in C++
While browsing the excellent modern C++ reactive console UI library FTXUI, I noticed this piece of code in how the events are typed/handled.
// event.hpp struct Event { // --- Constructor section --------------------------------------------------- static Event Character(char); static Event CursorReporting(std::string, int x, int y); // Other constructor methods // --- Arrow --- static const Event ArrowLeft; static const Event ArrowRight; static const Event ArrowUp; static const Event ArrowDown; // .... Other definitions etc....
My immediate reaction to this is that this feels weird - partially because I am not used to the author’s C++ style, in addition, the combination of static member variables sharing the parent-type, and static methods for constructors are really confusing to read.
Let’s dive into it to see how things work.
-
Python testing ecosystem
-
Common, stupid, but non-obvious C++ mistakes I made
Internet consensus tends to label C++ as a hard language; I like to think Cpp is a “deep” language. There are always rooms for improvement - doesn’t matter how long you have been coding in C++. The expressiveness and the depth of the language is double-edged. It is what makes C++ great, but also makes it daunting for new users. These are the mistakes I’ve made in my daily usage of C++. I hope they can be useful for other people to avoid them in the future.
1. Capture by reference on transient objects
Callbacks (lambda functions, function pointers, functors, or
std::bind
on static functions) are a common paradigm when you work with message queues, thread pools, or event based systems. Lambda and closures give you a lot of power - but too much power could often cause problems, consider the following code:
-
Rich D3 interactivity in Jekyll posts
This is me trying to reproduce the results in this Stack Overflow post and Dan Cole’s blog.
Things to keep in mind:
- Include morley.csv (Search local JavaScript for
/morley.csv
) - Include D3.js
- Include box.js
- Include the local CSS and JavaScript
Check out the code for this post on GitHub.
- Include morley.csv (Search local JavaScript for