Skip to content
Timur Gafarov edited this page Mar 5, 2024 · 51 revisions

1. Why this project has the same name as Dlib for C++?

Coincidence. dlib for D has no connection to Dlib for C++. Note: Dlib for C++ is usually written in title case, so it is suggested to write dlib for D in lowercase for less confusion. Also lowercase version looks nicer :3

2. Why not @nogc or betterC?

For historical and compatibility reasons. Core parts of dlib were written in 2011, long before @nogc. Initially we used GC, and later added support for GC-free programming style under the same API (such as SuperImage interface).

Strict independence from Phobos and GC, which is what guaranteed by betterC and @nogc, is not the goal of this project, and never was. If you need it (e.g, for embedded systems, ARM, WebASM, etc.), dlib in its current state probably is not for you.

3. Why matrix-vector multiplication is inverted?

The reason behind this is an optimization under common associativity rules. Generally speaking, there is actually only multiplication of two matrices, and matrix-vector multiplication is its special case. This operation is ambiguous, since you can see the vector as Nx1 matrix (column vector) or 1xN matrix (row vector). The order of operands (and hense results) will be different in each case.

Matrix multiplication involves multiplying each row vector of one matrix by each column vector of another matrix. So in first case (Nx1) we will do matrix * columnVector, in second case (1xN): rowVector * matrix.

In dlib, there is only one multiplication - matrix * columnVector, being the most common of the two. But it is written in 'inverse' (left-associative) order: columnVector * matrix (defined as opBinaryRight overload for Matrix type). This goes against conventional math notation, but computationally is a lot cheaper for long chained expressions.

For example, with normal syntax, expression m2 * m1 * v will be evaluated as one full matrix multiplication (64 float multiplications) and then one matrix-vector multiplication (9 float multiplications in affine case). With left-associative syntax, the equivalent v * m1 * m2 will cause only two matrix-vector multiplications (total 18 float multiplications in affine case). Without parentheses there is no way to force compiler choose optimal execution path, so left-associative syntax was preferred as more concise and requiring less caution from the programmer.

Also left-associative syntax is arguably easier to read.

4. Why DynamicArray and String don't use struct destructors to free the memory?

Struct destructors are very dangerous and should be avoided. They are called when a struct leaves a visibility scope, but this is not always expected when using structs. Since they live on a stack, they should keep their state when copied by value. Invalidating them at scope exit breaks copying by value. Struct destructors are called implicitly and can't be controlled by the user. Consequiently, they are not what their name implies, and they should not be used to deallocate memory.

5. Why the library doesn't use memory allocators despite having them?

All objects in dlib can be created using allocators. In fact, New/Delete mechanism uses them under the hood already. You can define your own allocator to use with New/Delete via globalAllocator property in dlib.core.memory. There is no built-in "unique allocator per object" policy because it is generally unclear at which structure level it should be supported (per module? per class? And what about allocations outside classes?). Allocators are not silver bullet and they are not always convenient from an API design standpoint.