GFA BASIC 32‎ > ‎How To‎ > ‎

02 Counting CPU Cores

posted Jul 7, 2015, 4:06 PM by Troy Cheek   [ updated Jul 7, 2015, 5:06 PM ]
There may come a time when you need to find out how many CPUs or cores or hyper-threads the computer your program is running on can use.  In this day and age, even cheap home systems may have more than one CPU, each CPU may have more than one core, or each CPU may be divided into two or more hyper-threads.  Or some combination.  According to the cute little sidebar gadget over in the corner of my screen, my computer has 8 CPUs.  As I know I only have one and only one (1) physical CPU, I figure I have 8 cores, or maybe 4 cores each capable of hyper-threading.  Or I'm confused and mixing my terms again.  For the rest of this article, I'm just going to refer to these as cores.

The point is that I recently discovered that GB32 (easier to type than GFA BASIC 32) can create multiple concurrent threads.  In this usage, a thread is a procedure or subroutine of a GB32 program that runs separately from and parallel to the main program.  I wrote a program where the main thread created pretty pictures and saved screen shots 25 times a second in uncompressed bitmap format (I'll show you how to do that in my next article).  This was so I could create a video later.  Unfortunately, uncompressed bitmap files are quite large.  I'd have to stop every so often when memory or the disk drive filled up and compress or convert the files.  This took time and really slowed down the creation of those pretty pictures.  I spawned a second thread which converted the BMP files into PNG (Portable Network Graphics) files at the same time the main thread was creating the BMP files (I'll show you how to do that in a future article).  Because the two tasks were running in different threads, the execution of which Windows divided among the 8 cores in my computer, the task completed in roughly half the time.  Even though I have 8 cores, I only created 2 threads because that was the extent that the task could be parallelized.  But what if you wanted to create more threads?

In most cases, especially if your computing task takes a lot of horsepower, you don't want to create more threads than you have cores.  Windows will try to distribute the load equally across cores.  While a single core can handle more than one task at a time (even a single core computer can run a multitasking operating system like Windows), if each task needs a lot of CPU cycles, Windows might spend more time shuffling the tasks around among the cores than actually letting the cores do any processing.  So, if you're dividing your high-power task into threads, you probably want to make sure that you don't create more threads than cores.  (If your task doesn't take a lot of horsepower, you don't have to worry about how many threads you're creating, but if it doesn't take a lot of horsepower, why are you dividing up the task in the first place?)

I know I have 8 cores in this computer, so it's easy for me to program for that.  You might know that your computer has 4 cores and think that you can program for that.  We're both wrong.  The programs we write might be run on another computer some day in the future.  We need a way to determine the number of cores inside the program we're writing.  Luckily, I know how to do that.

I freely admit that the following code is ripped from a sample program included with my GB32 distribution.  It's called HardwareInfo.G32 in the folder System.  Internally, it's called Systeminfo and was created by Georg Veichtlbauer, a programmer for GFA back in the day.  It shows all sorts of information about your computer, but unfortunately most of it is useless because the values it's testing for are outdated.  It think it was written back around 1999, so just imagine how much computers have changed since then.  About the only thing that still works is the Windows version information and the number of processors.  This is the code that counts processors:

' Copied from Systeminfo - (c) Georg Veichtlbauer
Local anzeige$, n&, Proz%

Declare Sub GetSystemInfo Lib "kernel32" (lpSystemInfo As SYSTEM_INFO)
  wProcessorArchitecture As Card
  wReserved As Card
  dwPageSize As Long
  lpMinimumApplicationAdress As Long
  lpMaximumApplicationAdress As Long
  dwActiveProcessorMask As Long
  dwNumberOfProcessors As Long
  dwProcessorType As Long
  dwAllocationGranularity As Long
  wProcessorLevel As Card
  wProcessorRevision As Card
End Type
Proz% = sInfo.dwNumberOfProcessors
anzeige$ = "Anzahl der Prozessoren: " + Str$(Proz%) + Chr$(13)
Message anzeige$, "Prozessor Info", MB_ICONINFORMATION | MB_TASKMODAL, n&

It's a mouthful, and not just because some of it is in German.  In spite of the best efforts of a grandfather and two college professors, my German is terrible.  But I think I have the general idea.

Local just defines some variables.  Anzeige means display, so this string is just the information we want to display later.  Proz is short for processors, so this variable will contain the number of processors once we determine that.  It's a Long aka Int32 aka can count numbers higher than 2 billion.  Imagine a computer with 2 billion cores.  I want one of those!

Declare Sub GetSystemInfo Lib "kernel32" means that we're going to load kernel32.dll which is a runtime load of a dynamically linked library.  The kernel32.dll is part of windows, and basically this command creates a way we can access functions/procedures/subroutines in this program and use them as if they were built-in GB32 commands.  In this case, we're going to access GetSystemInfo, which returns a complicated data structure defined by...

Type creates a data structure, in this case defined by many different variables of different types.  Most of the information it returns is meaningless for us right now, but dwNumberOfProcessors sound like something we can use.

Dim as well all know by now dimensions or declares a variable type.  We make sInfo the type of variable we just Typed.

We call GetSystemInfo which returns all that data in the variable sInfo.  We can access the information inside sInfo by adding a period and the name of the sub-variable within, in this case assigning a value to the variable Proz.

The other two lines just make the data prettier and display it in a message box.  We could have just as easily printed it to the screen in a window or simply used it internally.  The important thing is that, thanks to Georg, we have the information now.

Use it responsibly.