Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Waarom krijg ik teveel open bestanden als ik code in een functie stop?

1 view
Skip to first unread message

Cecil Westerhof

unread,
Dec 3, 2011, 12:11:59 PM12/3/11
to
Ik heb de volgende code:
#!/usr/bin/env bash

set -o errexit
set -o nounset

declare LOOP_STEPS=2000
declare -r REQUIRED_STRING="required time"

declare day
declare -i end
declare i
declare minutes
declare seconds
declare -i start
declare -i total

function doTests {
start=$(date +%s)
for i in $(seq ${LOOP_STEPS}) ; do
read minutes seconds day < <(date '+%M %S %d')
done
end=$(date +%s)
total=${end}-${start}
echo "${REQUIRED_STRING} values with read: ${total}"

echo
}

start=$(date +%s)
for i in $(seq ${LOOP_STEPS}) ; do
read minutes seconds day < <(date '+%M %S %d')
done
end=$(date +%s)
total=${end}-${start}
echo "${REQUIRED_STRING} values with read: ${total}"

doTests

Als doTests wordt aangeroepen (wat dezelfde code bevat als voor de
functie aanroep) krijg ik:
required time values with read: 4
/home/cecil/bin/test2.sh: redirection error: cannot duplicate fd: Too many open files
/home/cecil/bin/test2.sh: cannot make pipe for process substitution: Too many open files
/home/cecil/bin/test2.sh: cannot make pipe for process substitution: Too many open files
/home/cecil/bin/test2.sh: line 20: <(date '+%M %S %d'): ambiguous redirect

Dus als ik de code op top level plaats werkt het prima, maar als
ik het in een functie plaats, gaat het fout. Hoe komt dit?

Als ik de code voor de functie aanroep verwijder, gaat het nog
steeds fout. Het heeft er dus niet mee te maken dat de code voor
een tweede keer wordt uitgevoerd.

--
Cecil Westerhof
Senior Software Engineer
LinkedIn: http://www.linkedin.com/in/cecilwesterhof

Huub Reuver

unread,
Dec 4, 2011, 5:00:37 AM12/4/11
to
On 2011-12-03, Cecil Westerhof <Ce...@decebal.nl> wrote:
> Ik heb de volgende code:
> #!/usr/bin/env bash
..
> doTests
>
> Als doTests wordt aangeroepen (wat dezelfde code bevat als voor de
> functie aanroep) krijg ik:
> required time values with read: 4
> /home/cecil/bin/test2.sh: redirection error: cannot duplicate fd: Too many open files
> /home/cecil/bin/test2.sh: cannot make pipe for process substitution: Too many open files
> /home/cecil/bin/test2.sh: cannot make pipe for process substitution: Too many open files
> /home/cecil/bin/test2.sh: line 20: <(date '+%M %S %d'): ambiguous redirect
>
> Dus als ik de code op top level plaats werkt het prima, maar als
> ik het in een functie plaats, gaat het fout. Hoe komt dit?
>
> Als ik de code voor de functie aanroep verwijder, gaat het nog
> steeds fout. Het heeft er dus niet mee te maken dat de code voor
> een tweede keer wordt uitgevoerd.

Als ik de code uitvoer krijg ik dezelfde foutmelding, met in de logs:
denied resource overstep by requesting 1024 for RLIMIT_NOFILE against
limit 1024 for /bin/bash
Denk even aan de oude bekende "forkbug", meestal kun je met domme
programma's een pc wel plat of non-responsive krijgen.

Als ik "strace ./test.sh" uitvoer krijg ik veel uitvoer. Met LOOP_STEPS=2
wordt 4x open("/dev/fd/63", O_RDONLY) aangeroepen. 2x voor de main loop
en 2x voor de function loop.

In plaats van een test om snelheid van een functie te meten is dit meer
een test hoeveel files je mag openen.

Waarschijnlijk niet wat je uiteindelijk wil, maar kun je niet beter
'time' gebruiken om de tijd die een functie nodig heeft te testen?

Met vriendelijke groet,
Huub Reuver


$ strace ./test.sh 2> test.out
required time values with read: 1
required time values with read: 0

$ grep -i fd test.out
stat("/home/user", {st_mode=S_IFDIR|0755, st_size=24576, ...}) = 0
stat(".", {st_mode=S_IFDIR|0755, st_size=24576, ...}) = 0
stat(".", {st_mode=S_IFDIR|0755, st_size=24576, ...}) = 0
gettimeofday({1322990529, 949920}, NULL) = 0
fcntl(255, F_GETFD) = -1 EBADF (Bad file descriptor)
fcntl(255, F_SETFD, FD_CLOEXEC) = 0
wait4(-1, 0x38d6616fd44, WNOHANG, NULL) = -1 ECHILD (No child processes)
fcntl(63, F_GETFD) = -1 EBADF (Bad file descriptor)
open("/dev/fd/63", O_RDONLY) = 3
fcntl(0, F_GETFD) = 0
fcntl(0, F_DUPFD, 10) = 10
fcntl(0, F_GETFD) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
wait4(-1, 0x38d6616fdc4, WNOHANG, NULL) = -1 ECHILD (No child processes)
fcntl(10, F_GETFD) = 0x1 (flags FD_CLOEXEC)
fcntl(63, F_GETFD) = -1 EBADF (Bad file descriptor)
open("/dev/fd/63", O_RDONLY) = 3
fcntl(0, F_GETFD) = 0
fcntl(0, F_DUPFD, 10) = 10
fcntl(0, F_GETFD) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
wait4(-1, 0x38d6616fdc4, WNOHANG, NULL) = -1 ECHILD (No child processes)
fcntl(10, F_GETFD) = 0x1 (flags FD_CLOEXEC)
wait4(-1, 0x38d6616fd44, WNOHANG, NULL) = -1 ECHILD (No child processes)
fcntl(63, F_GETFD) = -1 EBADF (Bad file descriptor)
open("/dev/fd/63", O_RDONLY) = 3
fcntl(0, F_GETFD) = 0
fcntl(0, F_DUPFD, 10) = 10
fcntl(0, F_GETFD) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
fcntl(10, F_GETFD) = 0x1 (flags FD_CLOEXEC)
fcntl(63, F_GETFD) = 0
fcntl(62, F_GETFD) = -1 EBADF (Bad file descriptor)
open("/dev/fd/62", O_RDONLY) = 3
fcntl(0, F_GETFD) = 0
fcntl(0, F_DUPFD, 10) = 10
fcntl(0, F_GETFD) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
fcntl(10, F_GETFD) = 0x1 (flags FD_CLOEXEC)

Cecil Westerhof

unread,
Dec 4, 2011, 10:33:49 AM12/4/11
to
Op zondag 4 dec 2011 11:00 CET schreef Huub Reuver:
Maar waarom gaat het goed als ik de loop op top level heb en fout
indien dezelfde code in een functie staat? Het lijkt er dus op dat in
top level na gebruik de file pointers snel vrij worden gegeven en in de
functie niet, of in ieder geval later. Als ik een 'sleep 0.8' toevoeg
gaat het later fout.


> Waarschijnlijk niet wat je uiteindelijk wil, maar kun je niet beter
> 'time' gebruiken om de tijd die een functie nodig heeft te testen?

Dat geeft:
real 0m0.00s
user 0m0.00s
sys 0m0.00s
perc 42.68

Heb ik niet zo heel veel aan. :-{

Ik heb een aantal test in een test script staan, om te zien wat de
meer efficiënte methode is. Vanwege de snelheid van mijn PC ;-} is de
enige betrouwbare manier om waardes te krijgen de test erg vaak te
doen.

Huub Reuver

unread,
Dec 4, 2011, 11:39:21 AM12/4/11
to
Ja.
Misschien moet je je test aanpassen om de resultaten te krijgen die jij
wil.

Ben je nu een efficiente methode van files openen en sluiten aan het
vergelijken met de situatie dat je over je limieten gaat?

>> Waarschijnlijk niet wat je uiteindelijk wil, maar kun je niet beter
>> 'time' gebruiken om de tijd die een functie nodig heeft te testen?
>
> Dat geeft:
> real 0m0.00s
> user 0m0.00s
> sys 0m0.00s
> perc 42.68
>
> Heb ik niet zo heel veel aan. :-{
>
> Ik heb een aantal test in een test script staan, om te zien wat de
> meer efficiënte methode is. Vanwege de snelheid van mijn PC ;-} is de
> enige betrouwbare manier om waardes te krijgen de test erg vaak te
> doen.

Ik heb niet zo'n snelle pc. Voor dit soort testen is een netbook meestal
goed bruikbaar. Houdt de testen simpel:

$ time ./test.sh 1 1000

real 0m4.430s
user 0m0.308s
sys 0m0.652s
$ time ./test.sh 1000 1

real 0m4.906s
user 0m0.576s
sys 0m1.316s
$ cat test.sh
#!/usr/bin/env bash

set -o errexit
set -o nounset

declare STEPS1=$1
declare STEPS2=$2
declare day
declare i
declare minutes
declare seconds

function doTests {
for i in $(seq ${STEPS1}) ; do
read minutes seconds day < <(date '+%M %S %d')
done
}

for i in $(seq ${STEPS2}) ; do
read minutes seconds day < <(date '+%M %S %d')
done

doTests
$

Het kan vast nog simpeler...

Op het eerste gezicht lijkt bash vrij slordig om te gaan met functions.
Een strace kan je vertellen of je vergelijking goed is.

Ik weet niet wat jij optimaliseert, dus de optimalisatie zul je zelf
moeten doen. Het lijkt me dat als het echt niet veel meer moet doen
als bovenstaand script een C-programma efficienter zou moeten zijn.
Dan kun je bijvoorbeeld gettimeofday aanroepen zonder fork.

Als het programma 10x per dag wordt uitgevoerd en 1 sec. duurt dan heb
je weinig aan zo'n optimalisatie...

Cecil Westerhof

unread,
Dec 4, 2011, 1:34:44 PM12/4/11
to
Op zondag 4 dec 2011 17:39 CET schreef Huub Reuver:

>> Maar waarom gaat het goed als ik de loop op top level heb en fout
>> indien dezelfde code in een functie staat? Het lijkt er dus op dat in
>> top level na gebruik de file pointers snel vrij worden gegeven en in de
>> functie niet, of in ieder geval later. Als ik een 'sleep 0.8' toevoeg
>> gaat het later fout.
>
> Ja.
> Misschien moet je je test aanpassen om de resultaten te krijgen die jij
> wil.

Zelfs met 250 iteraties gaat het (na verloop van tijd) fout in een
functie, terwijl op top level 50.000 geen probleem is.

Ik denk dat het tijd wordt om lid te worden van gnu.bash.bug. ;-)


> Ben je nu een efficiente methode van files openen en sluiten aan het
> vergelijken met de situatie dat je over je limieten gaat?

Ik vergelijk:
temp=$(date '+%M %S %d')
minutes=${temp:0:2}
seconds=${temp:3:2}
day=${temp:6:2}
met:
read MINUTES SECONDS DAY < <(date '+%M %S %d')

Ik nam aan dat de tweede versie niet alleen duidelijker is, maar ook
sneller. Dat laatste blijkt niet het geval te zijn. Het is 4% trager.
Maar daar het duidelijker is, kan ik er wel mee leven. ;-)


> Ik weet niet wat jij optimaliseert, dus de optimalisatie zul je zelf
> moeten doen. Het lijkt me dat als het echt niet veel meer moet doen
> als bovenstaand script een C-programma efficienter zou moeten zijn.
> Dan kun je bijvoorbeeld gettimeofday aanroepen zonder fork.

Maar ik wil de performance van de verschillende bash constructies
meten. :-D


> Als het programma 10x per dag wordt uitgevoerd en 1 sec. duurt dan heb
> je weinig aan zo'n optimalisatie...

Het is niet zozeer dat het echt noodzakelijk is, maar ik wil het
gewoon weten. (Ik beklim de Mount Everest omdat hij er is.)

Ik doe dit soort dingen voor de Nederlandstalige Bash groep die ik heb
opgericht:
http://tinyurl.com/BashNederlands

Ik kom er trouwens net achter (ik had het ook op gnu,bash gepost) dat
het een bug is die in 4.2 is opgelost. Dus ik ga upgraden.

Cecil Westerhof

unread,
Dec 4, 2011, 3:31:42 PM12/4/11
to
Op zondag 4 dec 2011 19:34 CET schreef Cecil Westerhof:

> Ik kom er trouwens net achter (ik had het ook op gnu,bash gepost) dat
> het een bug is die in 4.2 is opgelost. Dus ik ga upgraden.

De upgrade heeft het probleem opgelost. Nu geeft (vijf keer 20.000
uitgevoerd):
for j in $(seq ${repetitions}) ; do
start=$(date +%s)
for i in $(seq ${LOOP_STEPS}) ; do
:
done
end=$(date +%s)
total=${end}-${start}
echo "${REQUIRED_STRING} for: ${total}"

if [[ ${CHECK_SUBSHELL} == 1 ]] ; then
start=$(date +%s)
for i in $(seq ${LOOP_STEPS}) ; do
temp=$(date '+%M %S %d')
minutes=${temp:0:2}
seconds=${temp:3:2}
day=${temp:6:2}
done
end=$(date +%s)
total=${end}-${start}
echo "${REQUIRED_STRING} values with temp variable: ${total}"

start=$(date +%s)
for i in $(seq ${LOOP_STEPS}) ; do
read minutes seconds day < <(date '+%M %S %d')
done
end=$(date +%s)
total=${end}-${start}
echo "${REQUIRED_STRING} values with read: ${total}"
fi

echo
done

als uitvoer:
required time for: 0
required time values with temp variable: 51
required time values with read: 61

required time for: 0
required time values with temp variable: 55
required time values with read: 63

required time for: 0
required time values with temp variable: 50
required time values with read: 58

required time for: 0
required time values with temp variable: 51
required time values with read: 58

required time for: 0
required time values with temp variable: 50
required time values with read: 58

De read is dus zo'n 4% minder efficiënt, maar heeft toch mijn
voorkeur. Hij is duidelijker en makkelijker aan te passen.

Op zich vind ik het trouwens wel vreemd dat het minder efficiënt is.
Als iemand een verklaring heeft …

Huub Reuver

unread,
Dec 4, 2011, 3:51:28 PM12/4/11
to
On 2011-12-04, Cecil Westerhof <Ce...@decebal.nl> wrote:
>
> De read is dus zo'n 4% minder efficiënt, maar heeft toch mijn
> voorkeur. Hij is duidelijker en makkelijker aan te passen.
>
> Op zich vind ik het trouwens wel vreemd dat het minder efficiënt is.
> Als iemand een verklaring heeft ???

Als ik de strace kijk lijkt het dat je met "read" de fd-driver opent en
met 8 read opdrachten 8 karakters leest.

Zelfs als niet-IT-er heb ik nog op school geleerd dat inlezen per regel
efficienter is. Natuurlijk zijn er allerlei checks en clean-up acties,
maar de overhead van 8 keer een karakter lezen is niet klein.

Misschien zijn hier experts die er meer van kunnen zeggen.

Cecil Westerhof

unread,
Dec 4, 2011, 5:49:16 PM12/4/11
to
Op zondag 4 dec 2011 21:51 CET schreef Huub Reuver:

>> De read is dus zo'n 4% minder efficiënt, maar heeft toch mijn
>> voorkeur. Hij is duidelijker en makkelijker aan te passen.
>>
>> Op zich vind ik het trouwens wel vreemd dat het minder efficiënt is.
>> Als iemand een verklaring heeft ???
>
> Als ik de strace kijk lijkt het dat je met "read" de fd-driver opent en
> met 8 read opdrachten 8 karakters leest.
>
> Zelfs als niet-IT-er heb ik nog op school geleerd dat inlezen per regel
> efficienter is. Natuurlijk zijn er allerlei checks en clean-up acties,
> maar de overhead van 8 keer een karakter lezen is niet klein.

Ik neem aan dat de:
temp=$(date '+%M %S %d')

iets soortgelijks doet, anders is de 4% verschil wel erg weinig.

Daarnaast het inlezen per regel is efficiënter bij disk i/o. Volgens
mij moet het bij lezen uit het geheugen weinig uitmaken.

Huub Reuver

unread,
Dec 5, 2011, 1:37:26 AM12/5/11
to
On 2011-12-04, Cecil Westerhof <Ce...@decebal.nl> wrote:
> Op zondag 4 dec 2011 21:51 CET schreef Huub Reuver:
>
>>> De read is dus zo'n 4% minder efficiënt, maar heeft toch mijn
>>> voorkeur. Hij is duidelijker en makkelijker aan te passen.
>>>
>>> Op zich vind ik het trouwens wel vreemd dat het minder efficiënt is.
>>> Als iemand een verklaring heeft ???
>>
>> Als ik de strace kijk lijkt het dat je met "read" de fd-driver opent en
>> met 8 read opdrachten 8 karakters leest.
>>
>> Zelfs als niet-IT-er heb ik nog op school geleerd dat inlezen per regel
>> efficienter is. Natuurlijk zijn er allerlei checks en clean-up acties,
>> maar de overhead van 8 keer een karakter lezen is niet klein.
>
> Ik neem aan dat de:
> temp=$(date '+%M %S %d')
>
> iets soortgelijks doet, anders is de 4% verschil wel erg weinig.
>
> Daarnaast het inlezen per regel is efficiënter bij disk i/o. Volgens
> mij moet het bij lezen uit het geheugen weinig uitmaken.

Waarom zou je dat aannemen, dat lees je toch zo af met een strace op een
enkele read-lus?

Met vriendelijke groet,
Huub Reuver


$ strace test.sh
..
open("/dev/fd/63", O_RDONLY|O_LARGEFILE) = 3
fcntl64(0, F_GETFD) = 0
fcntl64(0, F_DUPFD, 10) = 10
fcntl64(0, F_GETFD) = 0
fcntl64(10, F_SETFD, FD_CLOEXEC) = 0
dup2(3, 0) = 0
close(3) = 0
ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, 0xbe72c0f8) = -1 EINVAL (Invalid argument)
_llseek(0, 0, 0xbe72c160, SEEK_CUR) = -1 ESPIPE (Illegal seek)
read(0, "4", 1) = 1
read(0, "0", 1) = 1
read(0, " ", 1) = 1
read(0, "4", 1) = 1
read(0, "2", 1) = 1
read(0, " ", 1) = 1
read(0, "0", 1) = 1
read(0, "4", 1) = 1
--- SIGCHLD (Child exited) @ 0 (0) ---
..
read(3, "40 42 04\n", 128) = 9
read(3, "", 128) = 0
--- SIGCHLD (Child exited) @ 0 (0) ---
..

Paul van der Vlis

unread,
Dec 5, 2011, 3:13:56 AM12/5/11
to
Op 04-12-11 19:34, Cecil Westerhof schreef:

> Ik doe dit soort dingen voor de Nederlandstalige Bash groep die ik heb
> opgericht:
> http://tinyurl.com/BashNederlands

Leuk dat je een bash-groep hebt opgericht, daar had je wel wat meer
publiciteit voor kunnen maken (of heb ik het gewoon gemist?)

Maar hij lijkt voor mij als geen lid van Linked-in ontoegankelijk?

Groet,
Paul.



--
Paul van der Vlis Linux systeembeheer Groningen
http://www.vandervlis.nl

Cecil Westerhof

unread,
Dec 5, 2011, 2:32:10 PM12/5/11
to
Op maandag 5 dec 2011 09:13 CET schreef Paul van der Vlis:

> Op 04-12-11 19:34, Cecil Westerhof schreef:
>
>> Ik doe dit soort dingen voor de Nederlandstalige Bash groep die ik heb
>> opgericht:
>> http://tinyurl.com/BashNederlands
>
> Leuk dat je een bash-groep hebt opgericht, daar had je wel wat meer
> publiciteit voor kunnen maken (of heb ik het gewoon gemist?)

Het is al enige tijd geleden dat ik hem heb opgericht (2009 als ik het
me goed herinner) en ik heb deze nieuwsgroep net gevonden. Sowieso ben
ik beter in techniek, dan in het maken van publiciteit. :-(

Ik heb er geen enkel probleem mee indien je mensen voor wie de groep
interessant kan zijn op het bestaan wijst. :-D


> Maar hij lijkt voor mij als geen lid van Linked-in ontoegankelijk?

Je kunt de groep lezen zonder lid te worden van LinkedIn. Indien je
een reactie wilt geven zul je lid moeten worden van LinkedIn. (Lijkt
me niet echt een probleem.) Indien je ook zelf een discussie wilt
starten, moet je lid worden van de groep.

Paul van der Vlis

unread,
Dec 6, 2011, 1:18:27 PM12/6/11
to
Op 05-12-11 20:32, Cecil Westerhof schreef:
Ik weet niet precies waarom, maar ik zou niet snel lid worden van
Linked-in om lid te kunnen worden van zo'n groep ben ik bang.
En ik ben meer een vragen-steller dan een lurker...

De nieuwsgroepen hier zijn overigens ook vrij goed als het om bash gaat.
0 new messages