Forum: Ruby 10 millisecond delay/callback

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Earle C. (Guest)
on 2007-04-26 23:57
I need to perform a task every 10ms.  I've been using
kernel.sleep(0.01), but sleep seems to be slow and erratic at sub-second
resolutions.  Here's a test I ran along with the results:

irb(main):001:0> 0.upto(25) do
irb(main):002:1* puts Time.now.usec/1000
irb(main):003:1> STDOUT.flush
irb(main):004:1> sleep 0.01
irb(main):005:1> end
105
132
143
162
191
205
219
238
257
277
294
311
330
354
366
385
412
431
450
468
488
504
522
542
556
581
=> 0
irb(main):006:0>


So I'm wondering if there's a way to get a steady 10ms delay.  Another
option would be to set up an alarm or scheduled callback or something
every 10ms.  Is this possible.

Thanks,
Earle
unknown (Guest)
on 2007-04-27 00:07
(Received via mailing list)
On Fri, 27 Apr 2007, Earle C. wrote:

> 132
> 143
> 162
> 191

Try select()
----
irb(main):005:0> 0.upto(25) do
irb(main):006:1* select(nil,nil,nil,0.01)
irb(main):007:1> puts Time.now.usec/1000
irb(main):008:1> end
138
148
158
168
178
188
198
208
218
228
238
248
258
268
278
288
298
308
318
328
338
348
358
368
378
388



Kirk haines
Earle C. (Guest)
on 2007-04-27 01:02
unknown wrote:
> On Fri, 27 Apr 2007, Earle C. wrote:
>
>> 132
>> 143
>> 162
>> 191
>
> Try select()
> ----
> irb(main):005:0> 0.upto(25) do
> irb(main):006:1* select(nil,nil,nil,0.01)
> irb(main):007:1> puts Time.now.usec/1000
> irb(main):008:1> end
> 138
> 148
> 158
> 168

I just tried select and had the same results as sleep:

irb(main):001:0> 25.times do
irb(main):002:1* select(nil,nil,nil,0.01)
irb(main):003:1> puts Time.now.usec/1000
irb(main):004:1> end
787
797
814
839
858
877
894
912
932
951
965
985
1
21
39
62
81
100
117
134
154
174
193
212
228


FWIW, I'm using ruby 1.8.5 on Fedora Core 6 with kernel
2.6.20-1.2944.fc6.

Earle
Ganesh G. (Guest)
on 2007-04-27 01:19
(Received via mailing list)
Are you running Fedora inside a virtual machine?

Virtual machines like VMware, qemu are known to cause clock skews!

Cheers,
Ganesh G..
Earle C. (Guest)
on 2007-04-27 01:36
Ganesh G. wrote:
> Are you running Fedora inside a virtual machine?
>
> Virtual machines like VMware, qemu are known to cause clock skews!
>
> Cheers,
> Ganesh G..

Nope. No VM here.

Earle
Joel VanderWerf (Guest)
on 2007-04-27 02:04
(Received via mailing list)
removed_email_address@domain.invalid wrote:
...
> 178
> 188
> 198
...

What kind of system are you on? It doesn't work so well for me.

irb(main):017:0> [~] uname -a
Linux tumbleweed 2.6.20-15-generic #2 SMP Sun Apr 15 07:36:31 UTC 2007
i686 GNU/Linux
[~] ruby -v
ruby 1.8.6 (2007-03-13 patchlevel 0) [i686-linux]

rb(main):013:0> 0.upto(25) do
irb(main):014:1* select(nil,nil,nil,0.01)
irb(main):015:1> puts Time.now.usec/1000
irb(main):016:1> end
240
250
262
274
286
298
310
322
334
347
358
370
382
398
406
418
430
442
455
466
478
490
503
514
526
538
Gavin K. (Guest)
on 2007-04-27 02:10
(Received via mailing list)
On Apr 26, 2:06 pm, removed_email_address@domain.invalid wrote:
> On Fri, 27 Apr 2007, Earle C. wrote:
> > I need to perform a task every 10ms.  I've been using
> > kernel.sleep(0.01), but sleep seems to be slow and erratic at sub-second
> > resolutions.  Here's a test I ran along with the results:
[snip]
> Try select()
[snip]

No such glory on Windows:

irb(main):001:0> 0.upto(25) do
irb(main):002:1* select(nil,nil,nil,0.01)
irb(main):003:1> puts Time.now.usec/1000
irb(main):004:1> end
619
634
650
650
666
681
681
697
712
712
[snip]
=> 0
irb(main):005:0>

C:\Documents and Settings\gavin.kistner>ruby -v
ruby 1.8.5 (2006-08-25) [i386-mswin32]
Lionel B. (Guest)
on 2007-04-27 04:59
(Received via mailing list)
Joel VanderWerf wrote the following on 27.04.2007 00:04 :
>> 158
>> 168
>> 178
>> 188
>> 198
> ...
>
> What kind of system are you on? It doesn't work so well for me.

I'm wondering too, I see a nearly constant 2ms delay added to the 10ms
sleep with select on both an Athlon64 X2 3800+ and an Athlon XP 2400+.

Is there some YARV in there?

Lionel
John J. (Guest)
on 2007-04-27 09:02
(Received via mailing list)
On Apr 27, 2007, at 9:58 AM, Lionel B. wrote:

>>> 148
> sleep with select on both an Athlon64 X2 3800+ and an Athlon XP 2400+.
>
> Is there some YARV in there?
>
> Lionel
>
Have you all tried this outside of irb?
Just run the actual script itself + profiler to find out where the
slowdown is occurring.
Try it with sleep and select versions. This might show some different
points.. maybe not.
(Guest)
on 2007-04-27 10:46
(Received via mailing list)
It's unlikely to get 10ms intervals reliably even in C, so getting it
in ruby is very unlikely.

The reason it's hard to get it even in C is (as man setitimer says):
"Time values smaller than the resolution of the system clock are
rounded up to this resolution (typically 10 milliseconds)."

Even taking the system clock resolution out of the equation, if the GC
kicks in, it will definitely cause a delay greater than 10ms. Add in
the rest of the delays caused by ruby's runtime, and it's a loosing
game.

If you require running a loop at 10ms intervals, you're probably going
to have to dip down into C.

 - Evan P.
Michael W. Ryder (Guest)
on 2007-04-27 12:10
(Received via mailing list)
John J. wrote:
>>>> irb(main):007:1> puts Time.now.usec/1000
>>> What kind of system are you on? It doesn't work so well for me.
> slowdown is occurring.
> Try it with sleep and select versions. This might show some different
> points.. maybe not.
>

I tried your script on Windows under irb and using ruby and get similar
results.  I end up with times like 171.0, 171.0, 187.0, 203.0,
203.0,...,  obviously select is not waiting, at least under Windows.
Harry (Guest)
on 2007-04-27 12:41
(Received via mailing list)
On 4/27/07, Earle C. <removed_email_address@domain.invalid> wrote:
>
>
> So I'm wondering if there's a way to get a steady 10ms delay.  Another
> option would be to set up an alarm or scheduled callback or something
> every 10ms.  Is this possible.
>
>
>
I ran your code as a script and it worked fine on Windows XP.
I did not try irb.

Harry
Robert K. (Guest)
on 2007-04-27 13:11
(Received via mailing list)
On 27.04.2007 00:08, Phrogz wrote:
>
> 681
> 697
> 712
> 712
> [snip]
> => 0
> irb(main):005:0>
>
> C:\Documents and Settings\gavin.kistner>ruby -v
> ruby 1.8.5 (2006-08-25) [i386-mswin32]

This doesn't look too bad to me:

11:05:29 [~]: ruby -v
ruby 1.8.5 (2006-12-25 patchlevel 12) [i386-cygwin]
11:05:55 [~]: ruby -r enumerator <<SCRIPT
 > a = []
 > 20.times do
 > sleep 0.01
 > a << Time.now.usec / 1000.0
 > end
 > a.each_cons(2) {|x,y| puts y-x}
 > SCRIPT
11.0
11.0
10.0
11.0
11.0
11.0
10.0
11.0
11.0
11.0
10.0
11.0
11.0
11.0
10.0
11.0
11.0
11.0
10.0
11:07:04 [~]:

Kind regards

  robert
Edwin V. (Guest)
on 2007-04-27 14:14
removed_email_address@domain.invalid wrote:
> It's unlikely to get 10ms intervals reliably even in C, so getting it
> in ruby is very unlikely.
>
> The reason it's hard to get it even in C is (as man setitimer says):
> "Time values smaller than the resolution of the system clock are
> rounded up to this resolution (typically 10 milliseconds)."

I've been reading about this a bit lately and as far as I understand it,
windows has a 15ms "tick" (this might be different for different windows
versions)

The linux tick is by default 10ms. You can change linux to 1ms though
(1000HZ), by changing a kernel setting:
CONFIG_HZ_1000=y

I use 1000 HZ and my output is:
123
133
143
153
163
173
183
193
203
213
223
233


Edwin
unknown (Guest)
on 2007-04-27 16:23
(Received via mailing list)
On Fri, 27 Apr 2007, Joel VanderWerf wrote:

>> 158
>> 168
>> 178
>> 188
>> 198
> ...
>
> What kind of system are you on? It doesn't work so well for me.

Interesting.

It's an older 32 bit dual processor AMD box.  That was with Ruby 1.8.5.
OS is Linux Redhat Enterprise, but the Ruby was compiled locally.

I consistently get precise timings off of it, just like that.  I do not,
however, on another box -- a newer 64 bit dual core AMD, also running
Linux.  On that box a wait of 0.01 seems to fairly consistently wait
about
0.012.

I think that is about as precise as one is going to make the timings,
with
Ruby, in any case, though.


Kirk H.
Paul B. (Guest)
on 2007-04-27 17:39
(Received via mailing list)
On Fri, Apr 27, 2007 at 04:57:21AM +0900, Earle C. wrote:
> irb(main):001:0> 0.upto(25) do
> irb(main):002:1* puts Time.now.usec/1000
> irb(main):003:1> STDOUT.flush
> irb(main):004:1> sleep 0.01
> irb(main):005:1> end
> 105
> 132
> 143

I believe part of the problem is that you are not correcting for the
time that ruby spends in the loop.  In [ruby-talk:42003] Dave T.
proposed this solution:

  def every(period)
    base = last = Time.now.to_f
    count = 0

    loop do
      now = Time.now.to_f
      actual_secs = now - base
      expected_secs = period * count
      correction = expected_secs - actual_secs
      correction = -period if correction < -period
      select(nil, nil, nil, period + correction)
      now = Time.now
      last = now.to_f
      count += 1
      yield(now)
    end
  end

Which I've tested using this code:

  n = 0
  every(0.01) do
    puts Time.now.usec / 1000
    n += 1
    break if n == 25
  end

And got this output on Linux 2.6.5:

[pbrannan@zaphod tmp]$ ruby test.rb
37
47
57
67
77
87
97
107
117
127
137
147
157
167
177
187
197
207
217
227
237
247
257
267
277

You'll still get problems if the amount of time you spend doing work in
the loop exceeds 0.01 second.

Paul
Harold H. (Guest)
on 2007-04-27 19:58
(Received via mailing list)
On 4/27/07, Phrogz <removed_email_address@domain.invalid> wrote:
>
> [snip]

I think Time.now.usec on windows is just whack. :P

Here's a quick c extension for windows that can do super accurate
waiting:
///////////////////////////////////
#include "ruby.h"
#include "windows.h"

VALUE rb_cHotwait;
__int64 Hotwait_freq;

VALUE Hotwait_wait( VALUE inSelf, VALUE inWaitTime )
{
  __int64 theStartTime;
  __int64 theCounter;
  __int64 theDifference=0;
  double theWaitTime = NUM2DBL(inWaitTime);
  theWaitTime *= Hotwait_freq;
  QueryPerformanceCounter( &theStartTime );
  while( theDifference < theWaitTime )
  {
    QueryPerformanceCounter( &theCounter );
    theDifference = theCounter - theStartTime;
    Sleep(0);
  }

  return rb_float_new( (float)theDifference / (float)Hotwait_freq );
}

void Init_hotwait()
{
  QueryPerformanceFrequency( &Hotwait_freq );
  rb_cHotwait = rb_define_class( "Hotwait", rb_cObject );
  rb_define_singleton_method( rb_cHotwait, "wait", Hotwait_wait, 1 );
}
///////////////////////////////////

Used like this:
############
require 'hotwait'

10.times {
  puts "Time.now.usec   : #{Time.now.usec/1000}"
  puts "QueryPerfCounter: #{"%.4f"%Hotwait.wait( 0.01 )}"
}
############

It produces this:
Time.now.usec   : 718
QueryPerfCounter: 0.0100
Time.now.usec   : 718
QueryPerfCounter: 0.0100
Time.now.usec   : 734
QueryPerfCounter: 0.0100
Time.now.usec   : 750
QueryPerfCounter: 0.0100
Time.now.usec   : 750
QueryPerfCounter: 0.0100
Time.now.usec   : 765
QueryPerfCounter: 0.0100
Time.now.usec   : 781
QueryPerfCounter: 0.0100
Time.now.usec   : 796
QueryPerfCounter: 0.0100
Time.now.usec   : 796
QueryPerfCounter: 0.0100
Time.now.usec   : 812
QueryPerfCounter: 0.0100

And now it's past my bedtime. (:

-Harold
Earle C. (Guest)
on 2007-05-07 18:28
> I consistently get precise timings off of it, just like that.  I do not,
> however, on another box -- a newer 64 bit dual core AMD, also running
> Linux.  On that box a wait of 0.01 seems to fairly consistently wait
> about
> 0.012.
>
> I think that is about as precise as one is going to make the timings,
> with
> Ruby, in any case, though.
>
>
> Kirk H.

So I've done some more testing and I found something interesting:

Code:
#!/usr/bin/ruby -w

require 'enumerator'

a = []
15.times do
  sleep 0.008000499999999999
# sleep 0.0080004999999999999
  a << Time.now.usec / 1000.0
end
a.each_cons(2) {|x,y| puts y-x}


Results:
6.14199999999994
8.15700000000004
7.798
8.03800000000001
8.04099999999994
7.93900000000008
8.072
7.91099999999994
8.05799999999999
7.94299999999998
8.0
8.005
7.995
8.00100000000009

Now if use the second sleep expression instead of the first, I get the
following results:
11.978
12.016
11.963
12.073
11.939
12.06
12.235
11.696
12.0
12.047
11.956
12.004
11.996
12.0

Can anyone explain why the delay jumps from ~8ms to ~12ms?  I think this
may be the root of my problem.

Thanks,
Earle
Lloyd L. (Guest)
on 2007-05-08 00:17
Paul B. wrote:
>   def every(period)
# see above for the code
>   end
>
> Which I've tested using this code:
>
>   n = 0
>   every(0.01) do
>     puts Time.now.usec / 1000
>     n += 1
>     break if n == 25
>   end

I do have to say that, given what this code does and the time in which
it runs, saying that ruby just cannot run fast is silly.  I am very glad
to have seen this thread!
Paul B. (Guest)
on 2007-05-08 17:16
(Received via mailing list)
On Tue, May 08, 2007 at 07:12:12AM +0900, Lloyd L. wrote:
> I do have to say that, given what this code does and the time in which
> it runs, saying that ruby just cannot run fast is silly.  I am very
> glad to have seen this thread!

It depends on what you mean by fast.  For some applications I write, ten
milliseconds is a long time, and a hundred milliseconds is an eternity.

We do use ruby where appropriate, but not for latency-sensitive
applications.

Paul
Marcin R. (Guest)
on 2007-05-09 19:52
(Received via mailing list)
On Tuesday 08 May 2007 13:15, Paul B. wrote:
>
> Paul

ruby is interpreted langue and becouse of some restrictions on select
and
interpereter you can't get exact delays, the bigger delay is  more
acurate
it's but if you need that sort of precision consder including c code.
This topic is locked and can not be replied to.