Wednesday, January 01, 2020

Living on the stack with channels.

When I originally heard about being able to pin a container to a CPU to get performance gains with Kubernetes, I did not have a clear mental image of how this would actually play out.  Now, I am seeing a possible solution to that in the context of how a Quarterback container could use CPU pinning.

Running another thought experiment:

I originally had imagined that if a CPU was restricted to only seeing the data from a specific checkpoint in an evolving data stream, then by definition, all the CPU memory would be associated with that same snapshot of data.  It would be possible to guarantee a predictable state across a set of containers.

If you can scale down your workloads so the entire problem set for a partition of the data could be in a single container (single CPU cache), then maybe you could get better performance in the aggregated form than with a system that did not use CPU pinning and partition its code so that individual sections could be assigned their own CPU.

The quarterback component would orchestrate calls to other containers and they, in turn, could have preloaded structures in their pinned CPU cache (all CPUs are pinned to the same snapshot).

So arises the idea of "living on the stack"  You can have channels that will suspend state waiting for other data to appear.  The data being passed is not looked up at the time of channel passing.  It's already present in the code waiting to be executed.

Kubernetes could be used to orchestrate this dance: pinning CPUs and creating containers with multiple functions that could evolve from one responsibility to another in order to support phase transitions in a sequence of phases.  Don't reload the whole container, just switch duties.  Take for instance moving from calculation to aggregation.   In phase one of a two-phase execution sequence, the container could have methods to perform calculations.  In phase two maybe there is another process that uses the same data for aggregation.

It was also noted in earlier posts that because all the containers in a Pod can share the same file system, a local datastore could be used to share data between containers.  This data again would be pinned to the original snapshot.  A layer above this would yet another way to share data, this one at the node level.  This would be accomplished by using a queryable in-memory database.

Picture crop rotation here:  Farmers know that by alternating between corn and beans they can achieve better results.  In effect, the corn fertilizes the beans by leaving behind nutrients (in this case data)  As you move from phase to phase - reusing the containers by calling different functions you can build on the progress made by earlier processes.

The question is how to scale down the problem into chunks that could be optimized at this level.  As we start to envision that we are starting to approach the problem in a different way.  Instead of thinking about how to create long-running processes that handle many snapshots of data.  We start to see islands of computing power being created, shifting phases, and then disappearing.

This is the future I see in how containers can change the way programs are built.  We are already seeing a similar concept with the function frameworks out there.  I liken serverless and functional code to being "dynamic coding" whereas a system with CPU-pinning being something akin to "type safe".  Now your data engines are "type safe" with respect to snapshots of data.

Update:  1/2/2020

https://segment.com/blog/allocation-efficiency-in-high-performance-go-services/

This states:

The rule of thumb is: pointers point to data allocated on the heap.

and goes on to state:

Pointers should primarily be used to reflect ownership semantics and mutability. In practice, the use of pointers to avoid copies should be infrequent. Don’t fall into the trap of premature optimization. It’s good to develop a habit of passing data by value, only falling back to passing pointers when necessary. An extra bonus is the increased safety of eliminating nil.

Maybe since we are not working with mutable data we can use fewer pointers?

More research is cautioning against this pointing to the unpredictable nature of data size- the question is can we be assured that for a given dataset all the data can live on the stack which can be 8MB on Linux?  (running in docker / Kubernetes)

It seems that via data immutability we could determine the total size of data on the stack with some degree of confidence.

https://softwareengineering.stackexchange.com/questions/310658/how-much-stack-usage-is-too-much






No comments: