group the old and new ideas into misc
typed (file) still needs work, but at least the typed directory is gone
This commit is contained in:
@ -4,5 +4,3 @@
|
||||
%li= link_to "Parfait", "parfait.html"
|
||||
%li= link_to "Debugger", "debugger.html"
|
||||
%li= link_to "Memory" , "memory.html"
|
||||
%li= link_to "Threads" , "threads.html"
|
||||
%li= link_to "Optimisation ideas" , "optimisations.html"
|
||||
|
@ -1,75 +0,0 @@
|
||||
= render "pages/rubyx/menu"
|
||||
|
||||
%h1=title "Optimisation ideas"
|
||||
|
||||
%p I won’t manage to implement all of these idea in the beginning, so i just jot them down.
|
||||
%h3#avoid-dynamic-lookup Avoid dynamic lookup
|
||||
%p This off course is a broad topic, which may be seen under the topic of caching. Slightly wrongly though in my view, as avoiding them is really the aim. Especially for variables.
|
||||
%h4#i---instance-variables I - Instance Variables
|
||||
%p Ruby has dynamic instance variables, meaning you can add a new one at any time. This is as it should be.
|
||||
%p
|
||||
But this can easily lead to a dictionary/hash type of implementation. As variable “lookup” is probably
|
||||
%em the
|
||||
most
|
||||
common thing an OO system does, that leads to bad performance (unneccessarily).
|
||||
%p
|
||||
So instead we keep variables layed out c++ style, continous, array style, at the address of the object. Then we have
|
||||
to manage that in a dynamic manner. This (as i mentioned
|
||||
= succeed ")" do
|
||||
%a{:href => "memory.html"} here
|
||||
%p
|
||||
When a new variable is added, we create a
|
||||
%em new
|
||||
Type and change the Type of the object. We can do this as the Type will
|
||||
determine the Class of the object, which stays the same. The memory page mentions how this works with constant sized objects.
|
||||
%p So, Problem one fixed: instance variable access at O(1)
|
||||
%h4#ii---method-lookup II - Method lookup
|
||||
%p Off course that helps with Method access. All Methods are at the end variables on some (class) object. But as we can’t very well have the same (continuous) index for a given method name on all classes, it has to be looked up. Or does it?
|
||||
%p
|
||||
Well, yes it does, but maybe not more than once: We can conceivably store the result, except off course not in a dynamic
|
||||
structure as that would defeat the purpose.
|
||||
%p
|
||||
In fact there could be several caching strategies, possibly for different use cases, possibly determined by actual run-time
|
||||
measurements, but for now I just destribe a simeple one using Data-Blocks, Plocks.
|
||||
%p
|
||||
So at a call-site, we know the name of the function we want to call, and the object we want to call it on, and so have to
|
||||
find the actual function object, and by that the actual call address. In abstract terms we want to create a switch with
|
||||
3 cases and a default.
|
||||
%p
|
||||
So the code is something like, if first cache hit, call first cache , .. times three and if not do the dynamic lookup.
|
||||
The Plock can store those cache hits inside the code. So then we “just” need to get the cache loaded.
|
||||
%p Initializing the cached values is by normal lazy initialization. Ie we check for nil and if so we do the dynamic lookup, and store the result.
|
||||
%p
|
||||
Remember, we cache Type against function address. Since Types never change, we’re done. We could (as hinted above)
|
||||
do things with counters or robins, but that is for later.
|
||||
%p
|
||||
Alas: While Types are constant, darn the ruby, method implementations can actually change! And while it is tempting to
|
||||
just create a new Type for that too, that would mean going through existing objects and changing the Type, nischt gut.
|
||||
So we need change notifications, so when we cache, we must register a change listener and update the generated function,
|
||||
or at least nullify it.
|
||||
%h3#inlining Inlining
|
||||
%p
|
||||
Ok, this may not need too much explanation. Just work. It may be intersting to experiment how much this saves, and how much
|
||||
inlining is useful. I could imagine at some point it’s the register shuffling that determines the effort, not the
|
||||
actual call.
|
||||
%p Again the key is the update notifications when some of the inlined functions have changed.
|
||||
%p
|
||||
And it is important to code the functions so that they have a single exit point, otherwise it gets messy. Up to now this
|
||||
was quite simple, but then blocks and exceptions are undone.
|
||||
%h3#register-negotiation Register negotiation
|
||||
%p
|
||||
This is a little less baked, but it comes from the same idea as inlining. As calling functions is a lot of register
|
||||
shuffling, we could try to avoid some of that.
|
||||
%p More precisely, usually calling conventions have registers in which arguments are passed. And to call an “unknown”, ie any function, some kind of convention is neccessary.
|
||||
%p
|
||||
But on “cached” functions, where the function is know, it is possible to do something else. And since we have the source
|
||||
(ast) of the function around, we can do things previouly imposible.
|
||||
%p One such thing may be to recompile the function to acccept arguments exactly where they are in the calling function. Well, now that it’s written down. it does sound a lot like inlining, except without the inlining:-)
|
||||
%p
|
||||
An expansion if this idea would be to have a Negotiator on every function call. Meaning that the calling function would not
|
||||
do any shuffling, but instead call a Negotiator, and the Negotiator does the shuffling and calling of the function.
|
||||
This only really makes sense if the register shuffling information is encoded in the Negotiator object (and does not have
|
||||
to be passed).
|
||||
%p
|
||||
Negotiators could do some counting and do the recompiling when it seems worth it. The Negotiator would remove itself from
|
||||
the chain and connect called and new receiver directly. How much is in this i couldn’t say though.
|
@ -1,70 +0,0 @@
|
||||
= render "pages/rubyx/menu"
|
||||
|
||||
%h1= title "Threads are broken"
|
||||
|
||||
%p
|
||||
Having just read about rubys threads, i was moved to collect my thoughts on the topic. How this will influence implementation
|
||||
i am not sure yet. But good to get it out on paper as a basis for communication.
|
||||
%h3#processes Processes
|
||||
%p
|
||||
I find it helps to consider why we have threads. Before threads, unix had only processes and ipc,
|
||||
so inter-process-communication.
|
||||
%p
|
||||
Processes were a good idea, keeping each programm save from the mistakes of others by restricting access to the processes
|
||||
own memory. Each process had the view of “owning” the machine, being alone on the machine as it were. Each a small turing/
|
||||
von neumann machine.
|
||||
%p
|
||||
But one had to wait for io, the network and so it was difficult, or even impossible to get one process to use the machine
|
||||
to the hilt.
|
||||
%p
|
||||
IPC mechnisms were and are sockets, shared memory regions, files, each with their own sets of strengths, weaknesses and
|
||||
api’s, all deemed complicated and slow. Each switch encurs a process switch and processes are not lightweight structures.
|
||||
%h3#thread Thread
|
||||
%p
|
||||
And so threads were born as a lightweight mechanisms of getting more things done. Concurrently, because when the one
|
||||
thread is in a kernel call, it is suspended.
|
||||
%h4#green-or-fibre Green or fibre
|
||||
%p
|
||||
The first threads that people did without kernel support, were quickly found not to solve the problem so well. Because as any
|
||||
thread is calling the kernel, all threads stop. Not really that much won one might think, but wrongly.
|
||||
%p
|
||||
Now that Green threads are coming back in fashion as fibres they are used for lightweight concurrency, actor programming and
|
||||
we find that the different viewpoint can help to express some solutions more naturally.
|
||||
%h4#kernel-threads Kernel threads
|
||||
%p
|
||||
The real solution, where the kernel knows about threads and does the scheduling, took some while to become standard and
|
||||
makes processes more complicated a fair degree. Luckily we don’t code kernels and don’t have to worry.
|
||||
%p
|
||||
But we do have to deal with the issues that come up. The isse is off course data corruption. I don’t even want to go into
|
||||
how to fix this, or the different ways that have been introduced, because the main thrust becomes clear in the next chapter:
|
||||
%h3#broken-model Broken model
|
||||
%p
|
||||
My main point about threads is that they are one of the worse hacks, especially in a c environemnt. Processes had a good
|
||||
model of a programm with a global memory. The equivalent of threads would have been shared memory with
|
||||
%strong many
|
||||
programs
|
||||
connected. A nightmare. It even breaks that old turing idea and so it is very difficult to reason about what goes on in a
|
||||
multi threaded program, and the only ways this is achieved is by developing a more restrictive model.
|
||||
%p
|
||||
In essence the thread memory model is broken. Ideally i would not like to implement it, or if implemented, at least fix it
|
||||
first.
|
||||
%p But what is the fix? It is in essence what the process model was, ie each thread has it’s own memory.
|
||||
%h3#thread-memory Thread memory
|
||||
%p
|
||||
In OO it is possible to fix the thread model, just because we have no global memory access. In effect the memory model
|
||||
must be inverted: instead of almost all memory being shared by all threads and each thread having a small thread local
|
||||
storage, threads must have mostly thread specific data and a small amount of shared resources.
|
||||
%p
|
||||
A thread would thus work as a process used. In essence it can update any data it sees without restrictions. It must
|
||||
exchange data with other threads through specified global objects, that take the role of what ipc used to be.
|
||||
%p In an oo system this can be enforced by strict pass-by-value over thread borders.
|
||||
%p
|
||||
The itc (inter thread communication) objects are the only ones that need current thread synchronization techniques.
|
||||
The one mechanism that could cover all needs could be a simple lists.
|
||||
%h3#rubyx RubyX
|
||||
%p
|
||||
The original problem of what a program does during a kernel call could be solved by a very small number of kernel threads.
|
||||
Any kernel call would be listed and “c” threads would pick them up to execute them and return the result.
|
||||
%p
|
||||
All other threads could be managed as green threads. Threads may not share objects, other than a small number of system
|
||||
provided.
|
Reference in New Issue
Block a user