Take advantage of your multi-core processor
Posted by Paul Lefebvre
Nearly all CPUs used by your devices, from computers to tablets to phones, now contain multiple CPU cores. With a multi-core CPU, your computer can literally do multiple things at one time, which is called multiprocessing. And with a little careful planning, your Xojo apps can use multiprocessing for significant performance improvements in your apps.
You may be thinking, isn't that what threads are for? Sometimes, although not with Xojo. Threads in Xojo use co-operative threading (sometimes called fibers), which is simpler and more efficient but means that all the threads run on a single CPU core. Some other languages offer a pre-emptive threading model, but it can be extremely difficult and error-prone to work with pre-emptive threads. You have to correctly lock your resources and prevent sharing of data, among other things.
With Xojo, you have an easier way to take advantage of your multiple CPU cores: separate processes using console apps.
The technique is relatively simple. You create a console app that does the processing you need and returns a result. You then have your main (GUI) app start one or more console apps and supply them with the data they need to process. Each console app goes off on its own (in its own memory space and using its own CPU core) to do the processing. When one finishes it returns the result, which your app can then use.
An example will be worthwhile. Take a moment to download the example package here: ConsoleMultiprocessing.zip
The WordCounter Example
Imagine you are responsible for receiving documents to publish on the company newsletter and need to know the word counts. You used Xojo and created a quick app to add a bunch of documents to a window and go through and count all the words in each document, one by one.
The WordCounter example does this simple task of counting words in text files. The word count algorithm is not very speedy, but that is OK because the example also shows you how you use multiple CPU cores to speed it up.
There are actually two apps: WordCounterGUI, a desktop app, and WordCounter, a console app. The example can count words using three different techniques:
- Sequential: This simply counts the words in each file, one after the other in the main thread. This locks up the UI while the words are being counted.
- Threaded: This creates a separate thread for each file and the words are counted using the thread. The UI is reponsive while the words are being counted.
- Processes: This launches the WordCounter console app for each file and the words are counted by the console app. The UI is responsive while the words are being counted.
I used the sample documents included in the package (chapters from A Princess of Mars by Edgar Rice Burroughs) to test the different techniques.
As you might expect, the sequential method is pretty slow, taking about 44 seconds on my Mac Pro to count all the words in the four documents.
The thread method is slightly slower at 46 seconds. This may surprise you, but it shouldn't. As mentioned above, threads in Xojo are co-operative. This means they still run on just a single CPU core. Add in the extra overhead for managing the threads and you get a slightly slower time. But the threaded version is much nicer to use. The word counts appear in the order they are completed (shorter files to longer files) and the ProgressWheel spins while it is counting words.
The processes method is fastest. On my Mac Pro it finished in about 21 seconds. Four console apps were launched, one for each document, and each quickly took over its own CPU. As each console app finished, the results were returned back to the GUI and displayed.
Of course you will get different times on your system depending on the OS and number of CPU cores.
To try the app, run the built version for your OS platform in the WordCounterGUI Build folder. This folder has the console app set up properly for each platform. If you want to run WordCounterGUI directly from Xojo, you will need to make sure you copy the corresponding console app so that it is alongside the debug app (or change the code that finds the console app).
To use the app, click "Add File" to add one or more files (you can multi-select in the file selector dialog). You can then choose from Sequential, Threaded or Processes to choose the processing technique. Then press "Count Words" to count the words.
How Does it Work?
The WordCounterGUI app has four main project items: MainWindow, WordCounter, WordCountThread and WordCountShell.
MainWindow is the UI. It does not do any processing.
WordCountThread is a Thread subclass that uses WordCounter to count the words in a file. A separate instance of WordCountThread is created for each file added. MainWindow has a Timer to watch the progress of these threads so that the word counts can be updated in the ListBox and the elapsed time calculated.
WordCountShell is a Shell subclass (actually, an asynchronous shell) that starts a console app and waits for the word count result. A separate instance of WordCountShell is created for each file added. MainWindow has a Timer to watch the progress of these shells so that the word counts can be updated in the ListBox and the elapsed time calculated.
The console app is event simpler. It contains the same WordCounter class from above. In the Run event handler, it simply get the file path (passed as a parameter) and send it to a WordCounter instance to count the words in the file. The results are printed to the output and the console app quits.
Specifics About the Processes Technique
Look at the code in MainWindow.CountWordsInFilesUsingProcesses. The code is pretty simple: it loops through each file added to the ListBox and creates a new WordCountShell, passing in the file. Then the WordCountShell.CountWords method is called to count the words. When the console app outputs the word count, the DataAvailable event handler is called and the word count is saved in a property. The shell then stops running.
ShellTimer on MainWindow periodically checks to see if a shell has stopped running. If it has, then it gets its word count and updates the appropriate file in the ListBox.
WordCountShell.CountWords has the code that starts the console app using the shell and passes in the file name as a parameter. The shell runs asyncrhonously so that other processing can continue.
That is pretty much it. Overall it is a simple technique, but you can certainly make it more sophisticated. For example, an IPCSocket could be used in place of the shell for communicating with the console app. And this might make more sense if you want to have a fixed number of console apps running in the background, waiting to be assigned work. If you don't already have a Console license, you can purchase one in the Xojo Store (or upgrade to Xojo Pro to get a license for everything).