Very experienced Senior Software Architect & Engineer living in Los Angeles, CA. To write better software I've also worked professionally in many related disciplines (Analysis, DBA, SysAdmin, Network Engineering, Quality Assurance, etc).. Programming has been my primary passion since I started writing code when I was 8 years old.
In addition to extensive experience working in Software Architecture and Engineering/Development I’ve also worked professionally as a System Administrator, Network Engineer and Database Administrator. Which means I’m very used to setting up and running servers and networks.
I use those skills to provide myself with an excellent and optimized computing environment for my home office. Yes, admittedly, its overkill. But this way I have lots of options and very few limitations which is how I like it. This nicely supports My Natural Environment (my software development workstation I wrote about the other day)
Primarily my systems run Microsoft operation systems and servers (though I do have a smattering of Linux and others).
2 physical servers setup for virtualization (lots of cores & RAM)
3 Active Directory Domain Controllers (one physical, two virtual)
Microsoft SQL Server (running on SSD RAID-1 storage)
Development server that runs my visual control and internal web server
33.5 TB of RAID-6 via Microsoft Server with data deduplication
16 TB of RAID-5 backup storage (ONAP SAN)
400 Mbit Internet connection and 2 dedicated firewalls
Various managed network switches
Everything powered by active UPSes
I bought a nice half-rack cabinet last year to start transitioning my servers into a more manageable environment. I’ve converted a couple of companies (years ago) from individual servers and components sitting on tables to racks in the past and it was so much better. Finally got around to doing it for myself (or at least starting the process).
But my server closet is still the hub of my home computing where the Internet connection comes in and connects to my 2 firewalls and connects to the network switches for distribution. Also where a couple of my older servers are located. Its messy compared to the new rack cabinet but still effective. Eventually I’ll put all of this into a 2nd half-rack which will be better.
When I moved in to this house I converted one of the closets into a mini server room. I had an electrical outlet added and you can’t see it but I’ve got a small air conditioning unit and a bunch of UPSes sitting on the floor below the servers (which are on a shelf). I also wired the whole house for CAT-5 networking and there are 2 patch panels in the upper left of the closet which are also not visible.
I have two firewalls. I have a Sonos hardware firewall that the Internet connection plugs into and serves all of the general household computing (media devices, IoT devices, Alexa, Wemo, LIFX, etc). Then I have another, more beefy firewall that protects all of my real computing assets (workstation, servers, etc) and through the Sonos for Internet access. That may seem excessive, but IoT devices are notoriously unsafe and I don’t want my home office getting hacked just because I like to using Alexa to turn on and off my lights!
Plus its not displayed (and never will be) but I’ve also got 2 separate home alarm systems with 24 hour monitoring, one of which is a video surveillance system that records the entire outside of the house plus all of the doors and exposed windows. That all has backups and redundancies of course because I don’t want anyone walking off with my home office equipment!
The idea of software systems having layers has been around for quite awhile and the terminology is very helpful when used properly.
This article does not intend to cover the myriad of reasons why a software architect would choose to use, or not to use, layers in a software system. My off the cuff thought is any system that has more than 3 developers or more than 50,000 lines of code or would probably benefit from layers to some degree. Layers are certainly not needed for all software systems, but they are certainly helpful in some systems. Even in smaller systems they can be a useful conceptual idea, communication tool or handy for breaking up work by skill set.
A layer is all of the classes, types and related artifacts that are used to perform a particular type of function in a software system. The code that makes up layers is mostly application specific (written by the application developers specifically for use in that one system). Layer code does not mingle, all application specific code that exists in a layer exists in one and only one layer. Generic code (such as List, cryptography functions, string functions, etc) does not fall into a layer because it isn’t application specific. Ideally code that isn’t application specific and doesn’t fall into a layer should be written as reusable code and put in a library. Failing that the code that makes up layers in a system should at least exist in in separate and obviously named namespaces with non-layered code in different name spaces.
The most common layers of functionality used in software systems are the presentation layer, the service layer and the repository layer. If a systems is intended for use by other systems (not an end user) then an API layer would take the place of the presentation layer. Each of these common layers can be referred to by different names depending on who you talk to. Just like a rose, the name isn’t important because the purpose of the layer remains the same.
Presentation Layer, User Interface Layer, GUI Layer and Web Client Layer all refer to the same functionality of interacting with a person.
API Layer, Web API Layer and RESTful API Layer all refer to the same functionality of interacting with other external systems via a defined API.
Service Layer, Business Layer, Logic Layer and Business Logic Layer all refer to the same functionality of implementing the business rules, logic and complex processing within the software system.
Repository Layer and Data Access Layer both refer to the same functionality of reading and writing data to/from persistent storage.
The presentation / user interface layer is responsible for all user interaction. Business logic/rules and code that reads and writes data from persistent storage should not exist in this layer. The purpose of this layer is limited to displaying information and interacting with the user.
An API / Web API layer (instead of a user interface layer) for software systems that expose their functionality for use by other systems to use instead of a user interface. Functionally this layer takes the same role as the user interface layer would, it is responsible for all interfacing with the client.
The repository / data access layer is responsible for reading and writing data to/from persistent storage. Most often this is a relational database but it can be any type of structured storage (files, XML, no sql databases, etc).
The service / business logic layer is most of the code that exists between the presentation and repository layers. It is where the business logic and rules are implemented and where complex processing occurs. Business rules and logic should not be coded into other layers.
There can also be adapter layers positioned between the other layers in a system. For example an adapter layer between a Web API layer and a service layer that converts data from Data Transfer Objects (used in API) to the structures used by the service layer and then back into DTOs when data is returned from the service layer.
Systems can also include a proxy layer that replaces the service and repository layers for capabilities that are implemented by an external system. For example a complex web application system where the front end is implemented by one set of servers which use proxies to call into the Web API that is exposed by another set servers which contain the business rules, logic, processing and data access code.
Now that we have the fundamentals out of the way we can discuss why the concept of layers in software systems is important and still very much relevant today.
Without the concept of layers software systems would be big collections of objects that interact with each other. This would be troublesome because not all objects in a software system should be allowed to talk to each other. Layers are a top down design, code executing in a layer can call other code in the same layer or in the next layer lower down. But code can not call into layers above it and can’t skip over layers when calling downward. This is why the concept of layers is important in software systems!
Classes in the presentation/API layer should never directly talk to classes in the repository layer. Likewise classes in in the service layer should never call classes in the presentation or API layer. Classes in the data access layer should never call presentation/API or service layer classes. If we were to throw out the concept of layers and view software systems as a collection of classes that call each other then these very important concepts of separation would be lost.
A good system architecture codifies these layer concepts and makes them easier and more accessible for the application programmer while also reducing the likelihood of bad code that violates these principals. How this can be done in architecture is a difficult concept to express concisely and would require, at the very least, a sizable article of its own. I am working on transforming some of my existing personal library code into open source libraries that I’ll publish on NuGet in the foreseeable future. Those libraries will contain a very nice structured architecture I’ve used numerous times to implement these layer concepts in systems. If you’re interested in that keep an eye on my blog.
This is where I do my software development (unless I’m on-site in a cubicle somewhere).
I love working in my home office and find it to be an extremely effective and efficient environment for writing software!
Fast Intel i7 CPU at 4 GHz
32 GB of RAM
1 TB SSD for boot & temporary storage
Dual 4TB hard drives in RAID-1 for data storage
Nvidia GeForce GTX 1080 for driving my main 4K monitor
Nvidia GeForce GTX 1050 Ti for driving my 2 additional monitors
Main monitor is 55″ 4K which allows me to see A LOT OF CODE at once
Two secondary monitors for references or other information
Indirect, subdued lighting so the focus is the monitors
Music with a strong beat (electronic, industrial or metal) for motivation
Though normally the computer is sitting on the right side of the desk, not on top. My motherboard failed last week and I had to replace it. Historically I’ve preferred AMD based systems but decided to give Intel a try for the last upgrade. That has gone very poorly resulting in yet another hardware failure (see my previous post AMD vs Intel) .
I’m waiting for the 3rd generation of AMD Ryzen Threadripper processors to become available (hopefully later this year) for my next upgrade. Those are a game changer with all of their extra I/O bandwidth. I’ll also upgrade to a M.2 PCIe main drive and 64 GB of RAM at the same time. Should be nice.
In addition to the benchmarks themselves just as significant is the source code used to perform them. The benchmark application implements all of the synchronization methods being benchmarked plus also uses quite a lot of multi-threading itself. I figured other developers might find it illustrative or at least interesting to take a look at.
NOTICE: The application code is protected by
copyright and provided only for reference purposes. Please do not republish or copy any of the
code. The code works fine for my
purposes here but various portions of the code (namely the included interfaced
locking pattern implementations) have not been seriously tested and I would not
trust the code for production use! Other
portions, such as the logging and GUI synchronization code, are overly
simplistic and only applicable for this particular use. I have extensive libraries which have been
tested and used over many years which contain the locking, logging and other
code I use for production implementations.
None of this code was not taken from my libraries, all of this code was
thrown together over a weekend because I felt like playing with some locking
benchmarks.
I plan to release portions of my C# libraries via GitHub in
the future and have already started some of the refactoring and minor cleanup
necessary. That will include the
implementation of my Interfaced Thread Locking pattern along with much
else. Keep an eye on my blog at http://OriginalCoder.dev for news and
future developments.
The benchmarking application is written using WinForms and
uses a lot of multi-threading capabilities:
Creates numerous Thread instances that must be initialized, orchestrated and then cleaned up.
Performs all benchmarking in a background thread so that the GUI can continue being refreshed.
Displays ongoing progress of the benchmarking in the GUI.
Allows the user to cancel benchmarking via the GUI.
First a quick bit of background for anyone reading this who
hasn’t done multi-threaded programming.
When writing code that will be executed using multiple
threads there are a lot of important considerations. This is not an article about how to write multi-threaded
code, so I’m not going to cover most of those topics. The topic central to this article is
synchronizing access to shared data.
When multiple threads are reading and writing shared data great care
must be taken to make sure that data does not become corrupt and that the
individual threads get accurate data when reading. The more complicated and inter-related the
data is the more likely problems would occur without proper synchronization. But, to be clear, all multi-threaded code
should always use appropriate synchronization strategies to ensure the shared
data never gets corrupted and read operations don’t get bad data.
Essentially only a single thread should be allowed to write
to shared data at a time and no threads should be allowed to read the shared
data while a write is occurring. The
easiest way to implement this is to have a locking mechanism that only allows a
single thread to hold the lock at a time and then have any thread needing to
either read or write the data obtain the lock before doing so. This is the simplest implementation (such as
using the “lock” keyword) but can have performance issues if there
are many threads and most of the time they only read data because the threads
will block each other from reading simultaneously. For this reason, some
locking mechanisms (such as ReaderWriterLock) support separate read and write
locks which allow any number of threads to obtain a read lock simultaneously,
but make write locks exclusive including denying reads when a write lock is
held.
A quick example would be code in multiple threads working
with the same Dictionary<string, object> structure. If 2 separate threads were to make changes to
the dictionary at the same time the internal data structures could get
corrupted because the operations would likely step over each other. Only a single thread should be allowed to
write to shared data at one time, so an exclusive write lock should be obtained
before making any changes. That would
ensure that the Dictionary never becomes corrupt, but threads that only read
would still have problems if they are not included in synchronization. Consider a simple case where a thread gets
the Count of the dictionary which is then used to make a decision before
attempting to read specific keys. If the
reading thread gets the count then gets suspended (put on hold) while another
thread performs a write operation, when the reading thread continues the data
in the dictionary has changes so the count is different, but the reading thread
will continue doing its work as if the count was the same. This makes the work performed by the reading
thread invalid which is why reading also requires synchronization.
One other detail to note is that when implementing
multi-threaded code it is important to reduce the amount of time spent inside
any lock as much as possible. Because
many threads may be competing for access to the locks it is important to reduce
contention as much as possible. Never
obtain a lock and then perform a bunch of work / calculations inside the lock
when that work could have been done in preparation before obtaining the
lock. The ideal situation is where all
work and calculations can be performed outside of the lock and then only a few
quick & simple reads or writes are necessary from inside the lock. That is, of course, not always possible. If for example the total sum for a particular
property for all objects contained in a shared list is required, then a read
lock would need to be obtained before iterating through the list and computing
the total.
The following list of .NET Framework classes all provide
very similar capabilities to synchronize data in a multi-threaded
environment. All of these provide a
locking mechanism that, once obtained, any number of operations can be
performed while the lock is held. Most
of these only provide a single locking mechanism (for both reads and writes),
but a couple provide for separate read and write locks.
lock (keyword)
Monitor
Mutex
ReaderWriterLock
ReaderWriterLockSlim
Semaphore
SemaphoreSlim
In addition, there is the “lock” keyword built
into C# which is just a syntactic shortcut for using a Monitor. I do benchmark these separately below, but
for discussion I’ll just talk about Monitor.
Monitor and Mutex both provide the capability of a single
exclusive lock. When using either of
these both read and write operations obtain the same lock. This means that multiple reading threads will
block each other. If there were many
threads trying to read they would essentially queue up waiting for the lock and
then proceed through single file.
Depending on the exact implementation details this could remove any
advantages of using multiple threads.
Semaphore and SemaphoreSlim also provide the capability of a
single lock, but can potentially allow multiple threads to obtain one
simultaneously depending on how they are configured. For this article and the benchmarks I
configured the Semaphores so that they only allowed a single thread to obtain a
lock at one time which made them operate similarly to Monitor and Mutex. As such the same caveats above about readers
blocking each other and queueing up also apply to Semaphores in this context.
Both ReaderWriterLock and ReaderWriterLockSlim provide the
same capabilities, but are different implementations. These classes provide separate locks for
reading and writing where any number of readers are allowed simultaneously but
only a single write lock is allowed (which also blocks any readers when
held). Used properly these classes can
greatly improve performance when many threads are executing and most operations
are read only. Because these classes
provide more capabilities they can be implemented and used in different ways,
so I’ve benchmarked each of these classes 3 different ways. First using read locks when reading and then
only obtaining a write lock when a write is necessary. Second, always obtaining an upgradable read
lock and then upgrading it to a write when necessary. Lastly, I also benchmarked these where all
operations obtained a write lock. The
last one, only using write locks, is bad practice and makes these classes
operate similarly to a Monitor and Mutex (destroying the point of using these
instead of one of those). I do not at
all recommend ever doing that in code, but I decided to benchmark the write
only case just for informational comparison purposes.
Eliminating the use of either Reader/Writer class as
write-only, that gives us 9 different implementation possibilities (including
the “lock” keyword) for implementing data synchronization. That’s 5 different implementations to do
write-only locking and 4 different implementations to provide separate read and
write locks.
The Interlocked class is something of a special case when it
comes to data synchronization because it performs synchronization very
differently than the other options. The
above synchronization methods provide a locking mechanism that, once obtained,
any number of operations can be performed while the lock is held. But the Interlocked class provides methods
for atomic data synchronization. Atomic
means that the write operation occurs in a single step and will never conflict
with other threads. This only works for
a single, simple data type (such as an integer counter) and does not apply to
cases where there are multiple data items that are related and must be kept in
sync. For cases where Interlocked does
apply it is by far the fastest and easiest to use.
Interlocked offers a number of possible operations, for the
purpose of these benchmarks I only used Interlocked.Increment because the
benchmarking code was written around reading and incrementing a shared integer
counter.
Implementing locking code correctly can be a bit tricky and
it is all too easy to make a mistake that results in a lock never being
properly released. If a lock is acquired
and does not get released the system will freeze up. For this reason, locking should always be
implemented using try/finally blocks.
But even then it is quite easy to make a small mistake in the code that
could cause the lock not to be released under certain circumstances. Finding bugs of this nature is extremely
difficult and time consuming because they tend to occur unpredictably and
unless the system happens to freeze up for a developer while the debugger is
running figuring out exactly where the problem occurs can be nearly impossible.
For these reasons any pattern that greatly reduces or
eliminates the change of programmer error when using locks is highly
desirable. The lock keyword built into
C# does this because behind the scenes it automatically adds a try/finally
block that will always release the lock appropriately. This is great if the using Monitor for
locking aligns well with the work being done by the system. But it does not help in cases where Monitor
does not perform well (such as when there are many more concurrent reads than
writes).
The solution I came up with years ago (before the lock
keyword existed) was to create a pattern that uses a locking mechanism exposed
via an interface that implements IDisposable to handle releasing of the
lock. With this pattern locks are always
obtained via a using statement which will always ensure the lock gets released
correctly (much like the lock keyword).
I’ve found this pattern works extremely well and since adopting it I
can’t remember a single case where my code didn’t correctly release a lock.
Do note that this pattern may not be 100% perfect though (especially
depending on exactly how it is implemented).
In environments where a lot of threads get aborted using
ThreadAbortException it may be theoretically possible for the exception to get
thrown between when the lock is acquired and the using block takes effect. I’m not completely certain about this and
would need to carefully analyze the CIL that gets produced by the using
statement to figure it out. It would be
a rare occurrence in any case and would never be a meaningful issue for systems
that don’t utilize ThreadAbortException except during shutdown. This is important to be aware of when
considering adopting this pattern. In my
personal experience the elimination of coding errors for locks has been worth
the slight risks in the situations I’ve used this pattern. It is worth noting that I read something from
Microsoft (release notes?) that stated the lock keyword also had this same risk
under some circumstances in the past (I believe reading that they eventually fixed
it).
The lock keyword coding pattern and my interfaced locking
pattern are very similar except that the interfaced pattern requires
instantiating a class that implements the interface before it can be used. This can easily be viewed as a benefit though,
because it allows the developer to choose the underlying locking mechanism
instead of having it chosen for them (lock always uses Monitor).
The lock keyword coding pattern:
lock (someObject)
{
// perform work inside of lock
}
My interfaced locking pattern:
private readonly _lock = new ThreadLockReadWrite();
using (_lock.ReadLock())
{
// perform read-only work inside of lock
}
using (_lock.WriteLock())
{
// perform work inside of lock
}
using (_lock.ReadUpgradableLock())
{
// perform read work inside of lock
using (_lock.UpgradeLock())
{
// perform write work inside of lock
}
}
Benchmarking multi-threaded data synchronization isn’t as
simple as running a few benchmarks and picking the fastest. The environment in which the data
synchronization will occur has a very large impact on which implementation
performs the best. There are a few key
factors that help determine this which I’ve factored into the benchmarks.
Number of threads – How many threads will be running
simultaneously and needed access to the shared data is an important
consideration. Separate benchmarks for
1, 2, 10 and 100 threads were performed.
Reads vs Writes – How often threads will only need read
access compared to how often they will modify the data is a critical
factor. Separate benchmarks for writing
100%, 10% and 1% of the time were performed.
Amount of time locks are held – This can be a factor in the
decision making, but for these benchmarks I’ve used consistent delay times to similar
work being done inside read & write locks.
Since the goal is to keep time inside of locks to the minimal possible
and the biggest impact here is how much time is spent in read locks this
decision can be simplified. The key
factor here is that if threads need to do a lot of work inside read operations
then it is important to make sure reads do not bock each other. For these situations either ReaderWriterLock
or ReaderWriterLockSlim should always be used with separate locks for reading
and writing.
When the data to be synchronized is simple, not inter-related
and lends itself to atomic operations use the Interlocked class. While there are a few cases where the
Reader/Writer locks perform equally well there are no cases where anything
outperform Interlocked. So use it
whenever possible.
When most of the operations are writes – Use the lock
keyword. When most operations require
write access all of the locking mechanisms (except Interlocked, see above)
perform about the same. The lock keyword
provides a very nice coding pattern that prevents errors caused by locks not
getting released.
The API for using the ReaderWriterLockSlim class is easier
to work with compared to the ReaderWriterLock class. This is particularly true when dealing with
upgrading read locks to write locks. Unfortunately,
something is wrong inside the Slim class that can causes its lock upgrading to
execute very slowly so I would highly recommend against ever using
ReaderWriterLockSlim with upgradable locks.
When a lot of threads are involved the ReaderWriterLock (non-slim) class
also tends to outperform the Slim variant by a noticeable amount. Given those I’d recommend using
ReaderWriterLock at this time instead of ReaderWriterLockSlim.
WARNING: If using the ReaderWriterLockSlim class DO NOT
obtain an upgradable read lock and then upgrade to write locks. The benchmarks show that, for some reason,
this can execute VERY SLOWLY and tends to perform closer to mechanisms that
only supply a single (write) lock such as Monitor. Please note that, for whatever reason, this
does not happen with the ReaderWriterLock implementation only the Slim
variant! Sadly the API for the slim variant
is much easier to use and implementing read->upgrade with the non-slim class
is somewhat of a pain to code. This is
another case where my interfaced locking pattern would be very helpful (since
it hides the additional complexity required for the non-slim class).
When dealing with a modest number of threads obtaining a
read lock than then a write lock when needed appears to run a bit faster than
obtaining an upgradable read lock and then upgrading to a write lock when
needed. The catch is that with the
non-upgradable the read lock must be released before obtaining the write lock
which probably requires some additional thought to implement the threaded code
correctly. The performance difference
doesn’t seem to be very much so the reduced complexity of read->upgrade is
probably warranted for most circumstances.
Performance for read->upgrade was particularly good for
the benchmarks with 100 threads where reads were 99.9% (only 1 out of 1,000
operations was a write). So it may be
worth considering using the upgradable locks when dealing with very many
threads that only read the vast majority of the time. But as mentioned elsewhere in this document
never use the ReaderWriterLockSlim with upgradable locks because it has
internal problems.
My most interesting take away from these benchmarks is how
my interfaced locking pattern performs compared to direct, in-place
implementations. I had assumed that
since the pattern instantiates objects to handle the IDisposable which are then
discarded this additional work would have at least a small but noticeable
impact on performance. Surprisingly the
interfaced vs direct implementations of the same underlying mechanism tended to
perform equally well in the benchmarks.
This is because the amount of additional work for the interfaced pattern
is small and the amount of simulated work in the threads is much more significant. For cases where locks were held for extremely
short durations the ratio would change and the interfaced pattern may perform
slower than the direct alternative. But,
as noted above, the point of my interfaced locking pattern is to reduce bugs and
program freezes so a minor performance cost would be fine in most cases.
If a single thread will be used all of the locking
mechanisms are almost identical in their performance so which one is chosen
doesn’t matter much. Even
Interlock.Increment doesn’t execute faster when only a single thread is
used. Something to consider: If there is
only a single thread working with the data are you sure you need thread
synchronization? This might occur if
most of the time only a single thread will be running but under certain
circumstances / high load additional threads could be started. If that is the case I’d suggest optimizing
for when multiple threads are running.
Mostly writes – When most (or all) operations are
writes then Interlocked.Increment is about twice as fast as anything else and
all of the alternatives are roughly equal in performance. If possible use Interlocked, otherwise I’d
suggest using the lock keyword for these situations.
10x Reads – When reads occur 90% of the time
Interlocked.Increment is still fastest, but the Reader/Writer locks aren’t far
behind. All of the other mechanisms
(that only implement a single write lock) are equally slower. For high performance systems the additional
complexity of reader/writer locks with separate read & write locks would be
recommended. But the simplicity of the
lock keyword may outweigh the performance gains in some circumstances.
100x Reads – When reads occur 99% or more of the time
the Reader/Writer locks are almost as fast as Interlocked.Increment with all of
the other alternatives being much slower.
I’d definately recommend using a Reader/Writer with separate read &
write locks in all cases under these circumstances.
Mostly writes – When most (or all) operations are
writes then Interlocked.Increment is 10 times as fast as anything else and all
of the alternatives are roughly equal in performance. If possible use Interlocked, otherwise I’d
suggest using the lock keyword for these situations.
10x Reads – When reads occur 90% of the time
Interlocked.Increment is still fastest by a wide margin, so use that if
possible. The Reader/Writer locks
perform much better (about 3.5 times faster) than the other single (write only)
locking mechanisms. I’d recommend using
a Reader/Writer lock implementation with separate read & write locks under
these circumstances.
100x Reads – When reads occur 99% or more of the time
the Reader/Writer locks are almost as fast as Interlocked.Increment with all of
the other alternatives being much slower.
Separate Reader/Writer locks perform about 8.5 times faster than the
single (write only) lock alternatives.
I’d definitely recommend using a Reader/Writer with separate read &
write locks in all cases under these circumstances.
Mostly writes – When most (or all) operations are
writes then Interlocked.Increment is almost 100 times faster than anything
else! When possible definitely use
Interlocked. For locking situations that
can’t be handled with Interlocked I’d suggest using the lock keyword.
10x Reads – When reads occur 90% of the time
Interlocked.Increment is still by far fastest (~10 times faster than
Reader/Writer locks). When possible definitely
use Interlocked. If Interlocked can’t be
used the Reader/Writer locks perform about 10 times faster than their single
(write only) lock alternatives. I’d definitely
recommend using a Reader/Writer lock implementation with separate read &
write locks under these circumstances.
100x Reads – When reads occur 99% or more of the time the Interlocked.Increment still pulls ahead with this many threads involved. Interlocked.Increment is about 50% faster than using Reader/Writer locks but is far more restrictive. I’d probably use Interlocked if possible. Separate Reader/Writer locks perform about 15 times faster than the single (write only) lock alternatives. I’d definitely recommend using a Reader/Writer with separate read & write locks in all cases under these circumstances.
The human mind is incredible. It can accomplished great things though conscious thought, but what it can learn to internalize and do automatically is remarkable. As a result learning to internalize software development principals and concepts can allow developers to accomplish much more.
General Examples
Starting from a young age in school we are taught the alphabet, then words, sentances, paragraphs, essays and beyond. There are many details and rules that we are taught over years. Over time these get internalized by the brain and become automatic processes. Years after leaving school when we write something we don’t think about nouns, verbs, adjectives, grammar trees, etc. All of that knowledge has become mostly automatic, so we just sit down and write. By not having to consciously focus on those details it frees up our minds to think about higher level concepts like our goals for the writing, composition, etc.
Later on most people learn to drive. Driving involves following a bunch of rules, doing multiple things simultaneously and being aware of the situation and surroundings moment to moment. Not making mistakes is important because accidents are dangerous. When first starting out driving is really hard and kinda scary. It can feel overwhelming to do all of the things required constantly. But jump ahead after 10+ years of continuous daily driving and its so easy we don’t even pay conscious attention. The mind can internalize everything required for driving so thoroughly that one can drive between home and work and, upon arrival, not even be able to remember anything about the drive. Its like being on auto-pilot.
The human mind is able to internalize all sorts of things if the individual does them frequently for a long period of time. Years after something has been internalized it becomes difficult to consciously recall the rules and facts required by the task that has become automatic. This is beneficial because when things become automatically handled by our subconscious it frees up our conscious to think on a higher level about the task or (such as for driving) to do other unrelated things.
Software Development & Myself
Applying this to myself, I’ve spent so much time doing software development for so long that I’ve internalized a great deal.
I started programming when I was 7 or 8. By 11 I was pretty good; By 14 I was doing really complex, detailed programming. I really enjoyed development so I spent a ton of time learning as much as I could and constantly improving myself. I had no clue, but by the time I graduated high school I was significantly better at software development than most college graduates. From there I went on to tackle bigger, more complex and more unusual projects.
During my early years there were incredibly few books available, and none in normal book stores. If you could find them they had to be mail ordered. Thus I figured out most of the fundamental concepts, patterns, etc. myself before I ever read or heard about them. The “SOLID” principals had not been defined, but I figured them out for myself (except dependency inversion which wasn’t really practical at the time). I learned the concepts of object oriented programming and got very good at it 20+ years ago. I was doing serious, complex architecture work before I ever heard the term “software architecture”.
As a result of all that time and effort I’ve internalized a great deal of software development practices. I haven’t consciously thought about fundamental principals, OOP concepts or the like in many years. I don’t think about fundamental design patterns or optimization either. They have all become obvious to me now and they get applied automatically. As I write code all of that gets baked in automatically. Heck, I even subconsciously optimize my code, implementation and architectures on various levels with little thought. Its truly amazing what the subconscious can be taught to do. Which is great because having all that be subconscious allows me to consciously focus on business requirements, unusual aspects and the big picture.
As a side note, this is what partially (mostly?) inspired my logo.
Extrapolation & Early Development
I’d imagine anyone can learn to do this level of internalization given enough time. This is why spending a lot of time doing software development for a long time really pays off.
I do sometimes wonder if starting at a very early age makes a difference. Apparently being bilingual from a young age causes long-term changes in brain. Source code is kind of a language, I wonder if that has any long-term impact.
I’ve had OOMA VoIP service for a home phone line for about 3 years. The quality and cost were excellent, but I wasn’t using it enough and started getting MASSIVE spam marketing calls so cancelled it. Best cancellation experience I’ve ever had. They were very nice plus the suggested reselling my hardware, gave me tips and a transfer code to give the new owner 2 months free and 1 year hardware warranty. Wow. If you want a VoIP phone get OOMA, they rock.
The reason I’m getting all that massive spam marketing calls is my horrible mistake in registering a .US domain name. You can’t use a private registration service for .US domains, so your personal information gets associated with the domain name. Very shortly after that happened I started getting an insane number of calls from Indian web development firms wanting to “help me” with my new domain. I’m talking like 8-12 calls per day, every day. It lasted for 2 weeks before I set the VoIP service to reject all calls not on my contact list with a disconnect message. Turned that off after 2 months and I still get about a call a day! Insane.
My suggestion: Never, ever, ever, ever register a .US domain name (at least with you real contact information).
TL;DR: For two decades I’ve run 5-6 computers 24/7 with AMD CPUs and never had a hardware failure. A couple years ago I tried a top of the line Intel i7 and have had to replace the CPU once and the motherboard twice due to hardware failure. Ouch!
I worked at Intel for 5 years and it was a really good experience. Despite that, I’ve been a fan of AMD CPUs for ~2 decades (including while I was at Intel). The AMD processors always seemed to offer a better price / performance ratio. But now I’ve discovered a much more compelling argument against Intel.
I run a high-end primary workstation, a couple of other PCs and 5 servers, the workstation and servers run 24/7 and are on UPSs. Every couple of years I upgrade my workstation and the old parts (which were high-end at the time) either upgrade one of my servers or create a new server. At present all of the computers I have use AMD CPUs except my workstation.
A few years ago I decided to try Intel so built a new workstation around an Intel i7 4790K and Gigabyte z97x Gaming G1 Wifi Black motherboard. The reason for the “gaming” motherboard is I need 2 PCIe x16 slots for 2 good video cards to drive my 3 monitor setup. Just the CPU and motherboard combined cost around $1,200! The new workstation was very expensive but it did perform well.
Problem is that it wasn’t reliable. It worked reliably for about a year, then the motherboard failed and had to be replaced. Bought a one of the same model and swapped them. The system ran about about another year then the CPU died. So I had to buy a replacement 4790K and swap that. Its been about another year and I’m having motherboard issues again! It will only boot sometimes and it won’t boot with 32GB so I had to downgrade to 16GB. At this point I’ve had to replace both the CPU and motherboard and have just ordered a new motherboard (different brand this time).
The Intel CPU and motherboard were significantly more than AMD equivalents to start with, but having to replace the CPU once and the motherboard twice is ridiculous!
By contrast I literally can’t remember the last time I had an AMD CPU or motherboard fail. I have run some of them 24/7 for nearly 8 years and they never had a single hardware issue. But I try Intel and its failure city!
Regarding my next upgrade, I’m late because I’m waiting for new CPU models that mitigate against Spectre in hardware. I was going to upgrade to an AMD Threadripper but then Spectre happened. The 3rd generation Threadrippers based on the Zen 2 core are due out this year, then I’m upgrading. When I upgrade this Intel hardware IS NOT going into a server, I’m tossing it in the garbage! What a waste of money.
Anyone who has worked with me knows I’m fairly prolific. I tend to write a lot of reusable library & framework code. I hate writing the same code twice, so most of the code I write is reusable and ends up in libraries. Over the years I’ve built up a rather massive set of my own personal C# libraries.
I’ve started work reorganizing my C# libraries so they can be put up on NuGet. Some code is quite old so I’ll likely modernize it to use newer C# features. I already have some unit tests, but I’ll likely add some more. Putting everything up is a large undertaking but I’ll put pieces up as I go along.
Due to being me, I’ll certainly write new stuff along the way. Which is actually how I got here. As part of my Adventures in .NET Core I started writing a backend framework. I’ve written this sort of thing previously for clients and it makes backend development much quicker, easier and more standardized. But I’ve never written one of my own. Writing it for myself means I don’t have to compromises or worry about deadlines, so this should be fun.
I’ve been coding for a very long time. I’ve seen languages and industry paradigms come and go. JavaScript is all the rage currently, its even getting into the backend; But a decade from now it will be fading fast if not completely gone.
In the early years there was BASIC. It was everywhere and everyone knew it. It was designed to teaching programming concepts and not for wide spread production. Because it was so popular it was adopted for writing business applications and some sizable, complex systems were written in it. Those systems were hard to maintain because BASIC allowed programmers to write very messy code with little structure and many did. The term “spaghetti code” came to describe the very many BASIC applications who’s code was a complete mess. But because it was so popular a lot of programmers thought it was the end-all-be-all language. They even clung to it (aka Visual Basic) as the house of BASIC fell.
Then there was C. The language had been around but it took awhile for it to take hold in the world of personal computers. C was designed for low level system programming, was terse with few constraints on structure and had no type safety at all. It was never intended for widespread use as an application programming language. Yet it became the go-to language for writing large, complex business applications. Those systems were hard to maintain because C programmers used the lack of constraints to write messy code or code that was so “clever” others couldn’t understand it. Lint was created as an attempt to deal with some of these issues. Yet many programmers believed it was the be-all-end-all language.
Then there was C++. It was created to address some of C’s shortcomings and problems. It was an evolution of C so programmers mostly accepted it and moved over. C++ attempted to be better for application development by adding more structure and constraints to the language. But in trying to remain mostly compatible with C it still allowed for spaghetti code with very little type safety. Messy and very “clever” code was still pervasive and so Linters became even more important. Many programmers believed it was the be-all-end-all language. Some even clung to it (still do) as C++ was replaced by more modern languages.
Now there is JavaScript. In the beginning no one took it seriously, because it wasn’t intended for doing serious work. But it ended up in every web browsers and as the web became more important for business many developers were forced to use it. This made it “popular” and many libraries and frameworks were created to try and make it more suitable for business applications. Complex and sizable business applications are now written in it. Those systems are hard to maintain and typically get completely rewritten every few years to support a newly popular framework. Because it is so widely used programmers who have spent a lot of time in it are starting to believe it is the end-all-be-all language. It is even being used on the back end (Node.js) to write even larger, more complex systems.
See a trend? Very few programmers work professionally in BASIC or C today. Programmers used to more modern languages would likely find them antiquated and lacking if forced to use them. But at the time they were heralded as being great.
The writing is now on the wall that JavaScript will be joining them and fading away. That writing is Web Assembly. For the past couple of decades developers were literally forced to work in JavaScript because it was the only thing that ran client side on browsers. There was no choice. So a huge community has sprung up to support it. BASIC, C and C++ also had massive communities at their peek.
Once Web Assembly is mature and broadly adopted it will become possible to write client-side browser applications in many different languages. Web applications using Web Assembly will download and run faster on the browser than JavaScript. That alone would kill JavaScript client side. But many of those other languages will also be more modern and intended for writing business applications making them more productive and maintainable.
Some programmers will cling to JavaScript but most will end up moving to other languages. Once client-side JavaScript is dead server side will quickly follow and JavaScript will slowly fade away.
Its worth noting that there is a significant difference between BASIC, C/C++ and JavaScript though. BASIC was designed to teach programming concepts to students and it is still workable for that purpose. C/C++ were intended for low level systems programming (OS kernels, device drivers, embedded hardware, etc) where it is still used today and makes sense. JavaScript was created just to be used client side on web browsers. Once it is replaced in that domain its purpose to exist will be gone. Once it is no longer used in web browsers I expect it will completely disappar with no one using it for anything. It will just be a footnote in the history of the web.
Based on my experience with prior Framework versions of Entity Framework I set off to implement the new EF Core version using Database First. What I ended up finding is nothing like the experience I expected. This was the most frustrating experience I’ve had with Microsoft development tools in quite a long while.
TLDR: If you’re trying to do Database First with EF Core go get the EFCorePowerTools extension. This is what Microsoft should have provided and it will save you a lot of time and grief.
The .NET Framework versions of EF were both powerful and user friendly. Plus they gave the developer lots of options and capabilities so that they could use it the way that best made sense for the project. Tasks like generating or updating the data classes were very easy to accomplish using GUI windows. Sure the EDMX file could (rarely) get corrupted, but you are using version control, right? Just pull a known good version.
My goals for this project:
Use Database First because Code First doesn’t generate quality (DBA approved) schema unless you put in a lot of effort to hand code SQL DDL statements in the C# code. That’s a nightmare, so no.
Put the POCO classes in a .NET Standard library with minimal dependances so that they can be reused if ever needed (plus this keeps things clean)
Create a separate .NET Core library to contain all the Entity Framework specific code for implementing the data repository including the DbContext.
Reference both of those libraries from an ASP.NET Core MVC project to verify it works.
Seemed simple enough. I’ve done this numerous times with the old non-core versions of Entity Framework and it was easy. To get started I searched the Internet for instructions on how to do Database First with EF Core and I found quite a few walkthroughs. Unfortunately, none of them worked.
First problem I found is some of the walkthroughs assumed you were targeting an ASP.NET Core MVC project so they skipped steps. Found others that targeted a library but the first couple I tried failed because the list of EF Core packages has changed someone in the recent past. Finally got the library ready which included adding design and tool packages to the library because the POCO generator requires them. This library will end up getting used in production, it should not have dependencies on developer tooling and aids. Sloppy.
To generate the POCOs you have to execute a Scaffold-DbContext command in the Package Manager Console. This is not obvious and not user friendly. It also doesn’t work. I spent at least an hour and a half trying it with different arguemnts or even via the Command Prompt along with more Internet searching. It seems lots of developers have trouble with this because there are lots of pages dealing with various errors. Oh and there were multiple errors, every time I thought I fixed one thing a different error came up. It simply refused to work.
Then I got very lucky! Buried down in the comments on one of the many web pages was a link to the EFCorePowerTools Visual Studio extension. It adds GUI support for doing common tasks related to EF Core. One of those tasks is generating POCOs. Plus since its part of the GUI it doesn’t care what packages are added to the target library, so no need to add design and tooling to production libraries! Installed the extension, restarted Visual Studio and within 2 minutes I had the POCOs generated no problem.
Its worth mentioning that EFCorePowerTools is not from Microsoft! We have Erik Ejlskov Jensen to thank. Why in the world Microsoft didn’t do this themselves is beyond me. Very poor.
Before finding that extension I was keeping notes so I could write up my own web page on how to try and deal with Scaffold-DbContext. But after finding the extension I realized who cares and tossed them. Time will tell if it has any maintenance shortcomings, but for now it makes life so much better.