Recently I've decided to get into some performance issues and found out that in many cases when pages load a decent part of time is spent on get_list_of_timezones() function.
But this function, in turn, makes use of get_string() function: after fetching the list of timezones from database it calls get_string() for each timezone. In version 1.8.2 it happens 390 times. And each time get_string() tries to find the appropriate translation for a given timezone reading the language files and seeking only one string in them. But as get_list_of_timezones() is called twice, this process is repeated 780 times. The question is: why not to pass the whole array of timezones and process it at once? Or maybe there are some better options...
I really don't know if I'd better post it in the tracker.Sorry if posting it here was unappropriate.
I have just benchmarked this function, and the results are very surprising for me:
||Function time (en_utf8)
||Total time (en_utf8)
||Function time (ru_utf8)
||Total time (ru_utf8)
|Main, before login
|Main, after student's login
|Main, after admin's login
|The first page of some course, editing off
|The first page of some course, editing on
|Edit user's own profile
|View user's grades
|Edit course setitings
Page - is the page that was loading;
Function calls - number of calls to the get_string() function while loading the page;
Function time - the execution time of get_string() function while loading the page (separately for English and Russian locale), in seconds;
Total time - the execution time of the whole page, in seconds.
Note: the server was performing another tasks, so it makes sense to consider these timings only relatively to each other.
As you can see, the get_string() is called too many times on most pages. All it should do is just get the translated strings but the way it is done cannot be called efficient.
Few words about becnhmarking. I modified the get_string() function to count for how long and how many times it is executed (see attachment). In order to see the results I put the lines
$execution_time = 0;
$number_of_calls = 0;
in the beginning of the loading file (e.g. /index.php)
and the line
echo "execution time of get_string(): $execution_time seconds, called $number_of_calls times<br>";
in the end of the file.
So can you tell us a bit about your set-up. Which operating system, phpversion, moodle version, database, and so on. Obviously, get_string is doing something that is slow on your system, and ones like it, but which is fast everywhere else, and we need to fix that.
I have a quite old server 2 x 1 GHz, 896 MB, SCSI drives 10000 rpm running Windows Server 2003 Standard with latest patches, Apache 2.2.4, PHP 5.2.3 (running as module), MySQL 5.0.41-community-nt, and Moodle 1.8.2. There are also some other web-projects on this server (such as Drupal CMS) using the same Apache+PHP+MySQL but I don't see any performance problems there.
Using File Monitor during the mainpage loading I see that the httpd.exe process requests each language file for reading only once - you are right. But there are also many requests called "query information". For example, for /lang/en_utf8/timezones.php there are 145 such requests.
max_execution_time = 5
in php.ini, I always get an error inside get_string(). Updating language packs (Spanish in my case) helped somewhat but didn't solve the problem completely (Apache CPU time share still reaches 50% for 3-4 seconds while downloading the main page, even when I'm the only user connected).
I don't have a PHP accelerator installed. I'm going to try one and see if that helps.
I have installed XCache (as a dll extension), and it doesn't seem to help.
I am having the same issues. While loading files for the courses quite often I get this error. "too many requests received, please wait a minute and try again."
Is there a fix for this?
get_string() should be bound by the delay of reading the lang file from disk, parsing the PHP, and then doing the array lookup. As the array is cached, this price is only paid the first time you request something from the file.
An OS with good IO will show better scores for get_string() everytime. Lately that means recent releases of linux
- Does the machine have fast IO? Does bonnie++ think it has fast seeks?
- Can you make sure you do several runs of it so the lang files are in the kernel buffers? When doing multiple runs, ideally do one with cold-cache (on linux, echo 2 > /proc/sys/vm/drop_caches -- anywhere else, restart the OS) and save those numbers, and then do several runs hot-cache and average the hot-cache runs.
- In your profiling code -- it'd be great if you could capture numbers of the first time you call get_string() that will trigger the reading/parsing of a new lang file, and compare that with subsequent calls to get_string()
- Does it change if you run it with tuck-mmcache, eaccelerator or APC?
A combination of cached precompiled lang files should show get_string() to be very fast. If it doesn't, we need to profile it and make it better.
And perhaps we do have some IO that could be done better in there. Last time I reviewed the perf of get_string was around 1.4/1.5 when displaying the lang menu was 50% of the cost of the homepage -- and there wasn't anything obvious to my eyes.
Edit: wanted to say - great that you are benching parts of moodle. More of this needs to happen - and a better understanding of what is fast, and what is slow, emerge. We want to be hitting the fast paths of the code as much as possible, and making sure all the core functions/methods are fast fast fast.
$timezones[$timezone->name] = get_string(strtolower($timezone->name), 'timezones');
inside get_list_of_timezones() shows that the first call lasts for about 0.011 seconds while all the others - for about 0.004 seconds. But if we compare it with other functions it is still too long, especially for processing a cached data.
I know that Windows is not the best choice from the point of view of performance and maybe some day I'll change to Linux, but our college is tightly bound to this OS and we don't have a dedicated server for Moodle, so it won't be fast.
As I said in the previous answer I have SCSI drives 10000 rpm, the filesystem is NTFS. Actually, couldn't find bonnie++ clone for Windows to measure the seeks.
Windows restart doesn't lead to any improvements while loading one page several times leads to decreasing of get_string() calls and load time, though not significantly.
Could you say this in more details - which numbers I should capture? The duration of get_string() execution the first time it is called or what?
I don't have any accelerators now. The internal cache system was on all the time. When I turn it off the pages load only about 5 % longer.
Thanks for assistence
So script execution for the main admin page was 6-10 seconds.
I optimized the get_string() a bit (removed lookups of local module translations and other stuff that I will never need) and I'm down to 0.5-1 seconds per page now. This is quite acceptable, though Moodle could be generally more optimized.
The hardware is: Intel Xeon 2.4GHz, 2.5GB RAM, SCSI (1000rpm) RAID3.
And I'm using: Windows 2003, Apache 2.2.3, PHP 5.2.3 + eAccelerator 0.9.5.1, MySQL 5.0.37.
Latest Moodle 1.8.2+
I suppose someone could implement Moodle translations in gettext and do a comparison. I'm sure it would be more efficient.
Still 700 times sounds like way too much, I can't think of any pages that have that many strings ... exactly what URL were you on?
I honestly can't see gettext being much more efficient on Linux (I considered gettext and rejected it before the files were implemented in the current way).
It turns out, open_basedir restriction in php5 made all the 'include' and 'require' functions awfully slow.
I noticed this when Zend framework took 0.6s to generate a "hello world" page, and another similar server it did it in 0.03s. Now I'm down to 0.05s on Zend's "hello world"
And I have ~0.4s on Moodle. It gets down to ~0.3s with eaccelerator.check_mtime="0", though varies depending on the general server load (it serves as a Windows 2003 Domain, Exchange server, file server, etc.)
As for gettext, it would be interesting to do a benchmark on both approaches. With the include statements being fast as they are now, gettext might not give an adequate advantage.
Keep up the good work
One other thing to remember WRT gettext() is that when you use it in PHP, you have to be changing your locale all the time so gettext knows what locale, lang & collation to use.
Changing the locale means calls to putenv() and putenv() in PHP is very buggy, and gets broken/fixed/broken again quite often. The "breakage" is that putenv() uses pointers, badly, and ends up SIGSEGV'ing. And it gets worse if you are using turckmmcache or eaccelerator.
We had some really bad apache crashes that would leave the servers half-locked up. After much debugging over about 1 year I traced it down to mod/questionnaire which included something called ESP that had some gettext() calls, and therefore had to putenv().
Once I removed the putenv() calls it - it was like magic. No more crashing! Wohoo.
What happens if you tell eaccelerator to skip the stat()ing? I think there's an option in php.ini (eaccelerator.mtime?). Under windows, the stat() call is _very_ slow.
As an emergency fix, you can put in your config.php file a line saying
$CFG->rcache = false;
right after the lines that show the database connection details.
The same problem:
Fatal error: Call to undefined function _get() in /home/eadcom/public_html/lib/eaccelerator.class.php on line 56
<?php /// Moodle Configuration File
$CFG->dbtype = 'mysql';
$CFG->dbhost = 'localhost';
$CFG->dbname = 'eadcom';
$CFG->dbuser = 'eadcom';
$CFG->dbpass = 'eadcom';
$CFG->dbpersist = false;
$CFG->prefix = '_mdl';
$CFG->rcache = false;
$CFG->wwwroot = 'http://e-ead.com';
$CFG->dirroot = '/home/eadcom/public_html';
$CFG->dataroot = '/home/eadcom/moodle-eeadcom';
$CFG->admin = 'teste';
$CFG->directorypermissions = 00777; // try 02777 on a server in Safe Mode
// MAKE SURE WHEN YOU EDIT THIS FILE THAT THERE ARE NO SPACES, BLANK LINES,
// RETURNS, OR ANYTHING ELSE AFTER THE TWO CHARACTERS ON THE NEXT LINE.
$CFG->cachetype = 'internal';
You save me from an awful night...can't thank you enough.
Thank you and thank you and thank you!
First I wanted to offer my thanks for your suggestions in this thread. I was at my wit's end trying to figure out how to get back into my moodle after this same problem occurred for me.
I wonder if you might have a suggestion about how the settings for that page should be set. I have enabled the APC module on my server but I have no idea what the settings should be for the performance page. I don't want to screw it up again but uploading of files is painful for my learners.
The code was catering for the situation, but there was a subtle error in the logic.
Unfortunately, APC is not supported in rcache (Moodle still takes advantage of the main aspect of APC and other precompilers... the precompiler itself!).
Does APC have shmem abilities these days? If it does, we could have a plugin - just someone needs to write it
If the semantics of the shmem API are the same as for eaccelerator, then you can probably just make a copy of the eaccelerator class and hack it till it works.
And I have an initial implementation of this. All I need is testers
Implement the class...
And this fix
This is not strictly needed, but will help catch problems...
the whole patchseries is http://git.catalyst.net.nz/gitweb?p=moodle-r2.git;a=shortlog;h=mdl19-dbperf