"Thinking Clearly About Performance" by Cary Millsap:
or
var numbers = [1,2,3,4,5]
var doubled = []
for(var i = 0; i < numbers.length; i++) {
var newNumber = numbers[i] * 2
doubled.push(newNumber)
}
console.log(doubled) //=> [2,4,6,8,10]
doubled
array at each step until we are done.Array.map
function and look like:var numbers = [1,2,3,4,5] var doubled = numbers.map(function(n) { return n * 2 }) console.log(doubled) //=> [2,4,6,8,10]
map
creates a new array from an existing array, where
each element in the new array is created by passing the elements of the
original array into the function passed to map
(function(n) { return n*2 }
in this case).map
function does is abstract away the process of explicitly iterating over the array, and lets us focus on what
we want to happen. Note that the function we pass to map is pure; it
doesn't have any side effects (change any external state), it just takes
in a number and returns the number doubled.Or we could do it declaratively, using thevar numbers = [1,2,3,4,5] var total = 0 for(var i = 0; i < numbers.length; i++) { total += numbers[i] } console.log(total) //=> 15
reduce
function:var numbers = [1,2,3,4,5] var total = numbers.reduce(function(sum, n) { return sum + n }); console.log(total) //=> 15
reduce
boils a list down into a single value using the
given function. It takes the function and applies it to all the items in
the array. On each invocation, the first argument (sum
in this case) is the result of calling the function on the previous element, and the second (n
) is the current element. So in this case, for each element we add n
to sum
and return that on each step, leaving us with the sum of the entire array at the end.reduce
abstracts over the how and deals
with the iteration and state management side of things for us, giving us
a generic way of collapsing a list to a single value. All we have to do
is specify what we are looking for.map
or reduce
before, this will feel and look strange at first, I guarantee it. As programmers we are very used to specifying how
things should happen. "Iterate over this list", "if this then that",
"update this variable with this new value". Why should you have to learn
this slightly bizarre looking abstraction when you already know how to
tell the machine how to do things?how
it should happen.SELECT * from dogs
INNER JOIN owners
WHERE dogs.owner_id = owners.id
Yuck! Now, I'm not saying that SQL is always easy to understand, or necessarily obvious when you first see it, but it's a lot clearer than that mess.//dogs = [{name: 'Fido', owner_id: 1}, {...}, ... ] //owners = [{id: 1, name: 'Bob'}, {...}, ...] var dogsWithOwners = [] var dog, owner for(var di=0; di < dogs.length; di++) { dog = dogs[di] for(var oi=0; oi < owners.length; oi++) { owner = owners[oi] if (owner && dog.owner_id == owner.id) { dogsWithOwners.push({ dog: dog, owner: owner }) } }} }
data
array. To demonstrate what's going on we add a circle every second.It's not essential to understand exactly what's going on here (it will take a while to get your head around regardless), but the gist of it is this://var data = [{x: 5, y: 10}, {x: 20, y: 5}] var circles = svg.selectAll('circle') .data(data) circles.enter().append('circle') .attr('cx', function(d) { return d.x }) .attr('cy', function(d) { return d.y }) .attr('r', 0) .transition().duration(500) .attr('r', 5)
circle
s in the visualisation (initially there will be none). Then we bind some data to the selection (our data array)..enter()
method to get the datapoints which
have "entered". For those points, we say we would like a circle added to
the diagram, centered on the x
and y
values of the datapoint, with an initial radius of 0
but transitioned over half a second to a radius of 5
.I want this data drawn as circles, centered on the point specified in the data, and if there are any new circles you should add them and animate their radius.This is awesome, we haven't written a single loop, there is no state management here. Coding graphics is often hard, confusing and ugly, but here d3 has abstracted away most of the crap and left us to just specify what we want.
xkbcomp $DISPLAY xkb.dumpThis dumps the layout to the file xkb.dump, which I can then inspect. Doing so reveals that I want to edit the keys CAPS, LCTL, PRSC and RCTL. How to do this though, according to the x.org documentation, is that I need a rules file and a symbol file. The rules file references the symbol file in the 'options' field in order to enable the customizations I specified in the symbol file. The two examples I will use for these files are the evdev rule set located in
/usr/share/X11/xkb/rules/evdev
and the ctrl symbol file located at /usr/share/X11/xkb/symbols/ctrl
.ctrl:nocaps = +ctrl(nocaps)which points to the 'nocaps' entry in the 'symbols/ctrl' file.
/usr/share/X11/xkb/rules/
and symbol files must exist in /usr/share/X11/symbols/
. For ease of use, I created my files in ~/.xkb/
and created symlinks with in symbols/
and rules/
to those files. Once those custom files are set up, the rule file can be specified with setxkbmap. Changes in symbol files wont take affect until the cached versions are removed with rm /var/lib/xkb/*.xkm
. Then if our symbol file is /usr/share/X11/symbols/test
, with symbols defined as
partial modifier_keys
xkb_symbols "nocaps_nolrctrl" {
replace key <caps>; { [ Control_L, Control_L ] };
replace key <lctl> { [ NoSymbol ] };
replace key <rctl> { [ NoSymbol ] };
modifier_map Control { <caps> }
};
our option string to use would be 'test:nocaps_nolrctrl'. Test it with setxkbmap -option 'test:nocaps_nolrctrl'
. This is also what is needed in the rule file.
// Eliminate CapsLock, making it another Ctrl.
partial modifier_keys
xkb_symbols "nocaps_nolrctrl" {
replace key <caps> { [ Control_L, Control_L ] };
replace key <lctl> { [ NoSymbol ] };
replace key <rctl> { [ NoSymbol ] };
modifier_map Control { <caps> };
};
// Replace PtrSc
partial modifier_keys
xkb_symbols "prtsc_super" {
replace key <prsc> { [ Super_R, Super_R ] };
modifier_map Mod4 { <prsc>, <lwin> };
};
// Have [L|R]CTL generate an fkey
partial modifier_keys
xkb_symbols "ctl_fkey" {
replace key <lctl> { [ F12 ] };
replace key <rctl> { [ F12 ] };
};
~/.xkb/evdev.mouseless
and ~/.xkb/evdev.mouseless.lst
then added to the end of the
! option = symbols
section of evdev.mouseless:
window_ctrl:ctl_fkey = +window_ctrl(ctl_fkey)
window_ctrl:nocaps_nolrctrl = +window_ctrl(nocaps_nolrctrl)
window_ctrl:prtsc_super = +window_ctrl(prtsc_super)
setxkbmap -rules evdev.mouseless
-option "window_ctrl:nocaps_nolrctrl,window_ctrl:prtsc_super,window_ctrl:ctl_fkey"
.myModMask
with mod4Mask (mod4 being the equivalent to the Super/Windows key).
;; Setup move to previous window to mimic tmux bindings
(global-unset-key "\M-o")
(global-set-key "\M-o" 'other-window)
;; Move to next window
(global-set-key "\M-O" (lambda () (interactive) (other-window -1)))
set-option -g prefix F12
.config/autostart/synclient.desktop
[Desktop Entry]
Name=synclient
GenericName=synclient
Comment=Change trackpad to scrollpad
Exec=synclient RightEdge=1751
Terminal=true
Type=Application
StartupNotify=false
Windows 7 : =========== git clone https://github.com/TTimo/doom3.gpl.git
MacOS X : ========= git clone https://github.com/badsector/Doom3-for-MacOSX-
base
folder containing the Doom 3 assets. Since I did not want to waste time extracting them from the Doom 3 CDs and updating them: I downloaded the Steam version. It seems id Software team did the same since the Visual Studio project released still contains "+set fs_basepath C:\Program Files (x86)\Steam\steamapps\common\doom 3"
in the debug settings! neo
subfolder.Projects | Builds | Observations | |
Windows | MacO SX | ||
Game | gamex86.dll | gamex86.so | Doom3 gameplay |
Game-d3xp | gamex86.dll | gamex86.so | Doom3 eXPension (Ressurection) gameplay |
MayaImport | MayaImport.dll | - | Part of the assets creation toolchain: Loaded at runtime in order to open Maya files and import monsters, camera path and maps. |
Doom3 | Doom3.exe | Doom3.app | Doom 3 Engine |
TypeInfo | TypeInfo.exe | - | In-house RTTI helper: Generates GameTypeInfo.h : A map of all the Doom3 class types with each member size. This allow memory debugging via TypeInfo class. |
CurlLib | CurlLib.lib | - | HTTP client used to download files (Staticaly linked against gamex86.dll and doom3.exe). |
idLib | idLib.lib | idLib.a | id Software library. Includes parser,lexer,dictionary ... (Staticaly linked against gamex86.dll and doom3.exe). |
idlib.a
and gamex86.dll
but the core of the engine was still closed source.libc
is extensively used. Filesystem
) are in the Doom3.exe project. This is a problem since gamex86.dll needs to load assets as well. Those subsystems are dynamically loaded by gamex86.dll from doom3.exe (this is what the arrow materializes in the drawing). If we use a PE explorer on the DLL we can see that gamex86.dll export one method: GetGameAPI
:LoadLibrary
.GetGameAPI
in the dll using win32's GetProcAddress
.GetGameAPI
.gameExport_t * GetGameAPI_t( gameImport_t *import );
idGame
object and Game.dll has a pointer to a gameImport_t
object containing additional references to all missing subsystems such as idFileSystem
.typedef struct { int version; // API version idSys * sys; // non-portable system services idCommon * common; // common idCmdSystem * cmdSystem // console command system idCVarSystem * cvarSystem; // console variable system idFileSystem * fileSystem; // file system idNetworkSystem * networkSystem; // network system idRenderSystem * renderSystem; // render system idSoundSystem * soundSystem; // sound system idRenderModelManager * renderModelManager; // render model manager idUserInterfaceManager * uiManager; // user interface manager idDeclManager * declManager; // declaration manager idAASFileManager * AASFileManager; // AAS file manager idCollisionModelManager * collisionModelManager; // collision model manager } gameImport_t;
typedef struct { int version; // API version idGame * game; // interface to run the game idGameEdit * gameEdit; // interface for in-game editing } gameExport_t;
cloc
:./cloc-1.56.pl neo 2180 text files. 2002 unique files. 626 files ignored. http://cloc.sourceforge.net v 1.56 T=19.0 s (77.9 files/s, 47576.6 lines/s) ------------------------------------------------------------------------------- Language files blank comment code ------------------------------------------------------------------------------- C++ 517 87078 113107 366433 C/C++ Header 617 29833 27176 111105 C 171 11408 15566 53540 Bourne Shell 29 5399 6516 39966 make 43 1196 874 9121 m4 10 1079 232 9025 HTML 55 391 76 4142 Objective C++ 6 709 656 2606 Perl 10 523 411 2380 yacc 1 95 97 912 Python 10 108 182 895 Objective C 1 145 20 768 DOS Batch 5 0 0 61 Teamcenter def 4 3 0 51 Lisp 1 5 20 25 awk 1 2 1 17 ------------------------------------------------------------------------------- SUM: 1481 137974 164934 601047 -------------------------------------------------------------------------------
#Lines of code | Doom | idTech1 | idTech2 | idTech3 | idTech4 |
Engine | 39079 | 143855 | 135788 | 239398 | 601032 |
Tools | 341 | 11155 | 28140 | 128417 | - |
Total | 39420 | 155010 | 163928 | 367815 | 601032 |
lcc
codebase (the C compiler used to generate QVM bytecode) .idMath::InvSqrt
and spacial localization optimizations are here but most of the code just tries to use the tools when they are available (GPU Shaders, OpenGL VBO, SIMD, Altivec, SMP, L2 Optimizations (R_AddModelSurfaces
per model processing)...).const
placement).idCommonLocal commonLocal; // OS Specialized object idCommon * common = &commonLocal; // Interface pointer (since Init is OS dependent it is an abstract method int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { Sys_SetPhysicalWorkMemory( 192 << 20, 1024 << 20 ); //Min = 201,326,592 Max = 1,073,741,824 Sys_CreateConsole(); // Since the engine is multi-threaded mutexes are initialized here: One mutex per "critical" (concurrent execution) section of code. for (int i = 0; i < MAX_CRITICAL_SECTIONS; i++ ) { InitializeCriticalSection( &win32.criticalSections[i] ); } common->Init( 0, NULL, lpCmdLine ); // Assess how much VRAM is available (not done via OpenGL but OS call) Sys_StartAsyncThread(){ // The next look runs is a separate thread. while ( 1 ){ usleep( 16666 ); // Run at 60Hz common->Async(); // Do the job Sys_TriggerEvent( TRIGGER_EVENT_ONE ); // Unlock other thread waiting for inputs pthread_testcancel(); // Check if we have been cancelled by the main thread (on shutdown). } } Sys_ShowConsole while( 1 ){ Win_Frame(); // Show or hide the console common->Frame(){ session->Frame() // Game logic { for (int i = 0 ; i < gameTicsToRun ; i++ ) RunGameTic(){ game->RunFrame( &cmd ); // From this point execution jumps in the GameX86.dll address space. for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) ent->GetPhysics()->UpdateTime( time ); // let entities think } } session->UpdateScreen( false ); // normal, in-sequence screen update { renderSystem->BeginFrame idGame::Draw // Renderer front-end. Doesn't actually communicate with the GPU !! renderSystem->EndFrame R_IssueRenderCommands // Renderer back-end. Issue GPU optimized commands to the GPU. } } } }
Sys_StartAsyncThread
which indicate that Doom3 is multi-threaded. The goal of this thread is to handle the time-critical functions that the engine don't want limited to the frame rate:idCommonLocal commonLocal; // Implementation idCommon * common = &commonLocal; // Pointer for gamex86.dll
commonLocal
methods are called. The interface pointer is used during the handshake so doom3.exe
can exchange objects reference with gamex86.dll
but in this case the vtable cost is not optimized away. IN_frame()
.dmap
is a complete departure from the traditional bsp builder. I reviewed it to the deep down on a dedicated page.