Hi everyone!
I'm just posting this message because I want to share some little tips and some mistake we have made while developing PlayOnLinux in the past. I will try to post as many as I can if I find any interesting topic.
Disclamer: If you are an advanced developer, you may find this post a little simplistic. Indeed it is highly simplified. Its intent is to try to untangle the design of PlayOnLinux 4 and to explain what solution we are using in PlayOnLinux 5.
Chapter I - SetupWindow synchronization
The main target of this post is to explain an anti-pattern that we've used in PlayOnLinux 4, and also how to avoid it.
Context
We you are using POL_SetupWindow_* commands, you are able to create some messages like this one:
POL_SetupWindow_message "Hello World!"
If you have only one comand, it is fine. However let see what's happening when you try to show two messages in a row.
POL_SetupWindow_message "message 1" # Shows the message 1
# Wait until the user has clicked on the Next button
POL_SetupWindow_message "message 2" # Shows the message 2
The problem we want to solve in this topic is preventing the bash process to continue until the user has clicked on the next button. Otherwise, we would not have the time to see the first message.
We are going to assume that POL_SetupWindow_message is bound to a Python or Java message() method. We are not going to explain here the communication mechanism between bash and Python or between bash and Java, which is a different problem.
Let's take the following Python code:
message("message 1")
message("message 2")
The problem is: How can we wait for the user interaction before showing message number 2?
PlayOnLinux 4 approach: Busy waiting
This approach is an anti pattern. I'm just presenting it to explain why it should be avoided. To simplify the problem, we can assume that we have two differents threads in our program:
- Thread 1 : Thread of the UI
- Thread 2 : Thread of the script
The idea here is to block the thread 2 by creating a loop and periodically ask the UI if the user as clicked on the next button:
Pseudo-code (will not run) for the script Thread:
class ScriptThread():
def message(self, messageToShow):
UI.showMessage(messageToShow)
while(UI.hasClicked()):
thread.sleep(200)
Pseudo-code for the UI Thread:
class UIThread():
def showMessage(self, messageToShow):
self.hasClicked = False
# Call UI code that will show the message
def hasClicked(self):
return self.hasClicked
def eventOnClickNextButton(self):
self.hasClicked = True
PlayOnLinux 5 approach: Use of signals
The PlayOnLinux 4 approach is not acceptable in terms of performance. Not only are we going to loose at least 200ms (in this example), but we are also uselessly wasting CPU time.
In PlayOnLinux 5, we are going to use the Semaphore object. A semaphore that has two methods: acquire (decrement) and release (increment). A semaphore cannot be negative. If a thread try to decrease a semaphore whose value is equal to 0, it will be blocked until another thread increments it.
Let's have a look to the code:
Pseudo-code (will not run) for the script Thread:
class ScriptThread():
def message(self, messageToShow):
semaphore = Semaphore(0)
UI.showMessage(semaphore, messageToShow)
semaphore.acquire()
Pseudo-code for the UI Thread:
class UIThread():
def showMessage(self, semaphore, messageToShow):
self.semaphore = semaphore
# Call UI code that will show the message
def eventOnClickNextButton(self):
self.semaphore.release()
Performance comparison
Now we need to prove that the new design is more efficient by doing some tests. We are going to test the POL_SetupWindow_wait command, because it does not need any user interaction. We are going to use the following scripts:
Bash version (PlayOnLinux 4 and PlayOnLinux 5):
#!/bin/bash
source "$PLAYONLINUX/lib/sources"
test() {
POL_SetupWindow_Init
for ((i = 0; i <= "$1"; i++)); do
POL_SetupWindow_wait "$i"
done
POL_SetupWindow_Close
}
time test 100
time test 500
time test 1000
Python version (PlayOnLinux 5 only):
#!/usr/bin/env python
from com.playonlinux.framework.templates import Installer
import time
class Example(Installer):
logContext = "TestScript"
title = "title"
def test(self, numberOfIterations):
start = time.time()
setupWindow = self.getSetupWizard()
for i in xrange(numberOfIterations):
setupWindow.wait(str(i))
setupWindow.close()
end = time.time()
print end - start
def main(self):
self.test(100)
self.test(500)
self.test(1000)
The idea is to count how long does it take to run POL_SetupWindow_wait 100, 500 and 1000 times.
Here are the result on my Laptop:
The difference is pretty amazing. We can observe that it takes about 10 minutes to execute POL_SetupWindow_wait 1000 times in PlayOnLinux 4 while it takes only few seconds with PlayOnLinux 5.
Conclusion
Of course, we will never find scripts that run POL_SetupWindow_wait 1000 times. But still, it is always a good practice to avoid losing performances when it is not necessary.
The fact that PlayOnLinux 5 is written in Java may also interfere with this benchmark. Ideally, we would need to make a fourth test to spot the differences of performances due to the choice of the language.
Also you can notice that POLv5 Python (Jython scripts) are way faster than POLv5 bash scripts. This difference will be explained in a next post, so stay tuned :)
Edité par Tinou