4
\$\begingroup\$

I would like to get some performance improvement suggestions to a simple project I made using Bash in Linux.

The target is to read all the *.desktop files, and extract Name, Exec, Icon and Comment entries. Then will be displayed in a GTK yad List.

I have built the code in two versions. Both versions works OK, but are very slow.

Version 1 :

Read all the files one by one & grep the fields. This version need about 9 seconds to read & grep 300 desktop files.

TimeStarted=$(date +%s.%N)
files=/usr/share/applications/*.desktop
fileindex=1 
for i in $(ls $files); do
 readarray -t executable < <(grep -m 1 "^Exec=" $i |cut -f 2 -d '=')
 readarray -t comment < <(grep -m 1 "^Comment=" $i |cut -f 2 -d '=')
 readarray -t comment2 < <(grep -m 1 "^GenericName=" $i |cut -f 2 -d '=')
 readarray -t mname < <(grep -m 1 "^Name=" $i |cut -f 2 -d '=')
 readarray -t icon < <(grep -m 1 "^Icon=" $i |cut -f 2 -d '=')
 if [[ $comment = "" ]]; then
 comment=$comment2
 fi
 yadlist+=( "$fileindex" "${icon[0]}" "${mname[0]}" "$i" "${executable[0]}" "${comment[0]}" ) #this sets double quotes in each variable.
 fileindex=$(($fileindex+1))
done
TimeFinished=$(date +%s.%N); TimeDiff=$(echo "$TimeFinished - $TimeStarted" | bc -l)

Version 2:

grep all the files at once for required fields. This version improves the performance of the script, and needs 2 seconds to grep 300 desktop files.

TimeStarted=$(date +%s.%N)
files=/usr/share/applications/*.desktop
i=$files 
fileindex=1
IFS=$'\n'
readarray -t fi < <(printf '%s\n' $i)
readarray -t executable < <(grep -m 1 '^Exec=' $i)
readarray -t noexecutable < <(grep -L '^Exec=' $i)
readarray -t comment < <(grep -m 1 "^Comment=" $i )
readarray -t nocomment < <(grep -L "^Comment=" $i ) 
readarray -t comment2 < <(grep -m 1 "^GenericName=" $i )
readarray -t nocomment2 < <(grep -L "^GenericName=" $i )
readarray -t mname < <(grep -m 1 "^Name=" $i )
readarray -t nomname < <(grep -L "^Name=" $i ) 
readarray -t icon < <(grep -m 1 "^Icon=" $i )
readarray -t noicon < <(grep -L "^Icon=" $i ) 
for items1 in ${noexecutable[@]}; do
 executable+=($(echo "$items1"":Exec=None"))
done
for items2 in ${nocomment[@]}; do
 comment+=($(echo "$items2"":Comment=None"))
done
for items3 in ${nocomment2[@]}; do
 comment2+=($(echo "$items3"":GenericName=None"))
done
for items4 in ${nomname[@]}; do
 mname+=($(echo "$items4"":Name=None"))
done
for items5 in ${noicon[@]}; do
 icon+=($(echo "$items5"":Icon=None"))
done
sortexecutable=($(sort <<<"${executable[*]}"))
sortcomment=($(sort <<<"${comment[*]}"))
sortcomment2=($(sort <<<"${comment2[*]}"))
sortmname=($(sort <<<"${mname[*]}"))
sorticon=($(sort <<<"${icon[*]}"))
trimexecutable=($(grep -Po '(?<=Exec=)[ --0-9A-Za-z/]*' <<<"${sortexecutable[*]}"))
trimcomment=($(grep -Po '(?<=Comment=)[ --0-9A-Za-z/]*' <<<"${sortcomment[*]}"))
trimcomment2=($(grep -Po '(?<=GenericName=)[ --0-9A-Za-z/]*' <<<"${sortcomment2[*]}"))
trimmname=($(grep -Po '(?<=Name=)[ --0-9A-Za-z/]*' <<<"${sortmname[*]}"))
trimicon=($(grep -Po '(?<=Icon=)[ --0-9A-Za-z/]*' <<<"${sorticon[*]}"))
unset IFS
ae=0
for aeitem in ${fi[@]};do
 if [[ ${trimcomment[ae]} = "None" ]]; then
 trimcomment[ae]=${trimcomment2[ae]}
 fi
 yadlist+=( "$fileindex" "${trimicon[$ae]}" "${trimmname[$ae]}" "${fi[$ae]}" "${trimexecutable[$ae]}" "${trimcomment[$ae]}" ) #this sets double quotes in each variable.
 fileindex=$(($fileindex+1))
 ae=$(($ae+1))
done
TimeFinished=$(date +%s.%N); TimeDiff=$(echo "$TimeFinished - $TimeStarted" | bc -l)

Remarks:

a) Some .dekstop files do not include all the required fields.

b) Performance refers to 64-bit Intel Celeron N3050 - 4GB ram machine, running 64bit Debian 8 Sid with XFCE and GNU bash 4.4.0(1) and GNU grep 2.26. PS: Performance of 9 or 2 seconds is also verified by time ./script.sh.

c) The version 2 script performance can achieve below 0.5 seconds if I remove the "for" sections, but then yadlist becomes a chaos due to the missing fields in some .desktop files.

Result:

According to my opinion, even 2 seconds to grep 300 files it is still too much time for such a small number of files.

Is it possible to further optimize this scripts performance in Bash?

As a sample , you can have a look at this caja.desktop file, taken from my system. Notice that Comment entry is missing.

[Desktop Entry]
Name=Caja
Name[af]=Caja
<More Name entries for different locale>
GenericName=File Manager
GenericName[af]=Lêerbestuurder
<more GenericName entries for different locale>
Exec=caja
Icon=system-file-manager
Terminal=false
Type=Application
StartupNotify=true
NoDisplay=true
OnlyShowIn=MATE;

In other .desktop files, the comment entry (if present) looks like this:

Comment=View multi-page documents
<various Comment entries for different locale>
Mast
13.8k12 gold badges55 silver badges127 bronze badges
asked Nov 7, 2016 at 12:41
\$\endgroup\$
2
  • \$\begingroup\$ Welcome to codereview. Could you please post an example of a .desktop file ? \$\endgroup\$ Commented Nov 7, 2016 at 12:47
  • \$\begingroup\$ Hello! Nice to meet you all. I added a real desktop file sample in my main question. \$\endgroup\$ Commented Nov 7, 2016 at 13:28

2 Answers 2

1
\$\begingroup\$

Review w.r.t the algorithm only (language independent):

  • 5 grep per file to extract what you need. Instead search for all five altogether : grep "A|B|C|D|E". If this doesn't suit your requirement, you should write a simple file read program and extract all the 5 parameters in one file read instead of 5.
  • Calculate comment2 only if [[ $comment = "" ]];
answered Nov 7, 2016 at 16:55
\$\endgroup\$
3
  • \$\begingroup\$ One array with Grep of all five entries at once is fast indeed, but if one of the entries is completely missing, then array is messed up since grep returns nothing for the missing entry. Manipulating this messed array afterwards is slower than 5 greps. \$\endgroup\$ Commented Nov 7, 2016 at 17:16
  • \$\begingroup\$ By the way, your advise for comment2 is good. Thanks. \$\endgroup\$ Commented Nov 7, 2016 at 17:18
  • \$\begingroup\$ @George: If you cannot use the OR functionality , I would suggest reading the file yourself instead of grep. That would be faster. \$\endgroup\$ Commented Nov 7, 2016 at 17:27
0
\$\begingroup\$

After a lot of research and "observation" i found the problem....

The real problem of script limited performance was cpu scaling. As soon as i pushed the processor to work in full power (1.6 GHz), version 2 achieved 0.5 seconds!

All i had to do was to check the script performance in another machine, and i was lucky enough this "other" machine not to have cpu scaling enabled.

As a programmer point of view there is no doubt that version 2 is MUCH faster than version 1. Also it seems that version 2 is the most we can get out of bash.

PS1: I adopted recommendation of "thepace" for calculation of comment2. That way script performance improved by some milliseconds.

PS2: To make my CPU to work in full power i had to disable intel_pstate and apply performance governor in cpufrequtils (cpufreq-set -c 0 -g performance - same for -c 1) or even better to stick the CPU at max power using cpufreq-set -c 0 -f 1600000.

PS3: performance governor is also available with intel_ptate enabled (default setting) but in reality intel pstate keeps manipulating - reducing the cpu speed even in performance governor as proved by cpufreq-info (in a better way though than default powersave governor).By disabling intel pstate and applying performance governor cpu sticks to 1,6GHz.

PS4: I had no idea that cpufrequtils is installed by default in Debian 8...

For those who want to give a try, full script can be found here: https://github.com/gevasiliou/PythonTests/blob/master/appslist.sh

If you don't have .desktop files in your system (usually found at /usr/share/applications/) you can download this folder with around 300 files for testing: https://github.com/gevasiliou/PythonTests/tree/master/appsfiles

answered Nov 11, 2016 at 3:03
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Another pitfall with benchmarking these things are disk caches. Try, for example, time find ~ -name unlikely. It took 3.6 s the first time and 0.05 s the second time I ran it on my current machine. \$\endgroup\$ Commented Jan 10, 2017 at 14:49
  • \$\begingroup\$ @5gon12eder Very interesting info. Thanks! \$\endgroup\$ Commented Jan 10, 2017 at 14:54

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.