tomcat-users mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Christopher Schultz <ch...@christopherschultz.net>
Subject Re: [OT] Setting HTTP response headers caching for 1 year doesn't work
Date Wed, 19 Jan 2011 03:10:47 GMT
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

André,

(You always make me write so much code!)

On 1/18/2011 7:12 PM, André Warnier wrote:
> Christopher Schultz wrote:
>> There's nothing magical about the first operand: you can cast any of the
>> operands to trigger this promotion. 
> 
> Not quite true I think. See note below.

You're right: you have to do it before the first truncation.

> Well here you see the difference with Java.
> Java seems to overflow without even a warning.

Note that it's the Java compiler not the runtime that fails to deliver
an error. The compiled C code happily overflows without any warning. Had
I written the code using an array to store the operands and used an int
to store the temporary result, you'd see the same truncation behavior
with no compiler warnings. The fact that constants are being used are
allowing the compiler to complain in this case.

Revised code:

$ cat longtime.c
#include <stdio.h>

int main(int argc, char *argv[]) {
  int cacheTime1 = (1000 * 60 * 60 * 24 * 100 * 3);
  long cacheTime2 = ((long)1000 * 60 * 60 * 24 * 100 * 3);
  long long cacheTime3 = ((long long)1000 * 60 * 60 * 24 * 100 * 3);
  long long cacheTime4 = (1000 * 60 * 60 * 24 * 100 * (long long)3);

  printf("%d\n%ld\n%lld\n%lld\n", cacheTime1, cacheTime2, cacheTime3,
cacheTime4);

  return 0;
}
cschultz@dev:~/projects/toys$ cc -ansi -pedantic -Wall -o longtime \
    longtime.c && ./longtime
longtime.c: In function `main':
longtime.c:4: warning: integer overflow in expression
longtime.c:5: warning: integer overflow in expression
longtime.c:6: warning: ISO C90 does not support `long long'
longtime.c:6: warning: ISO C90 does not support `long long'
longtime.c:7: warning: ISO C90 does not support `long long'
longtime.c:7: warning: integer overflow in expression
longtime.c:7: warning: ISO C90 does not support `long long'
longtime.c:9: warning: ISO C90 does not support the `ll' printf length
modifier
longtime.c:9: warning: ISO C90 does not support the `ll' printf length
modifier
150196224
150196224
25920000000
150196224

So, the expression type escalation needs to happen before the first
overflow in order for things to work out well.

> Now the calculation overflows an int at the penultimate step (* 100),
> and setting the last term to be a long does not help, it's too late.

Not enough people use the word "penultimate": nice work.

> I think that what happens is :
> - the expression is evaluated from left to right, not in one single step
> - multiplying an int by a long (in whatever order), is done in long mode
> and generates a long

Correct on both counts.

>> Most languages' behavior are surprising to Perl programmers :)
> 
> See below.
> 
>>
>> Seriously, though, these are the tradeoffs of a strongly-typed language:
>> you have to be very precise about the way you want calculations to be
>> performed. The compiler assumes that if you wanted double-precision
>> integers, you'd ensure that the calculation is done using "long" and not
>> "int". Presumably, 64-bit operations are (or were) considered slower
>> than 32-bit operations and so require the coder to specify that 64-bit
>> operations are desired.
>>
> At the cost of truncating the result and obtaining an incorrect one
> surrepticiously and without a warning ?

Well, at some point (such as non-constant calculations) overflow checks
are either not possible (Java?) or not practical (C) at runtime. C and
similar languages are well-documented to silently ignore overflow (or
underflow for that matter) and it's up to the programmer to check for
this kind of thing. Overflow checking is something that takes time, and
the C language was designed to compile to minimal machine code. If the
programmer cares about overflow, the programmer needs to check for it.

Likewise, if a Java programmer wants to avoid overflow in all cases, he
or she only need to use BigInteger for everything. Of course,
performance totally sucks when using BigInteger, but hey, I don't think
you'll ever overflow that (unless your BigInteger has something like
2^16 digits in it).

> I would find : (long)(1000 * 60 * 60 * 24 * 365)
> more intuitive, if it forced the calculation to be done in Long mode
> (which I suspect it does not; it probably just "promotes" the truncated
> result to long before the assignment).

It does not: see the C code I posted. I would be surprised if Java acted
differently, though I'm unwilling to write the code myself just now to
test it.

>> No, the clarity is in the fact that the target identifier should be able
>> to store a double-precision value. It says nothing about calculations
>> that are assigned to it.
>>
>> If you saw this, would the result (0.0) confuse you?
>>
>> double d = 1 / 2;
> 
> It would not confuse me, because by now I am ready for anything.  But I
> would find this just as unintuitive and wrong, for exactly the same
> reason.  What kind of stupid compiler is this, which requires me to say
> double d = 1.0 / 2;
> to get a correct result ?

It's one that produces seriously fast code:

$ cat divisionSpeedTest.c
#include <stdio.h>
#include <sys/time.h>

#define ITERATIONS 100000000

int main(int argc, char *argv[]) {
  struct timeval start, end;

  int i;

  int integral;
  int ileft=1, iright=2;

  float decimal;
  float fleft = 1.0, fright=2.0;

  /* time empty loop */
  gettimeofday(&start, NULL); /* start clock */

  for(i=0; i<ITERATIONS; ++i)
    ;

  gettimeofday(&end, NULL); /* stop clock */

  long adjustment = (long)(end.tv_sec - start.tv_sec) * 1000
      + (((end.tv_usec - start.tv_usec) / 1000.0) + 0.5);

  printf("Look with no work took %ldms\n", adjustment);

  gettimeofday(&start, NULL); /* start clock */

  for(i=0; i<ITERATIONS; ++i)
    integral = ileft / iright;

  gettimeofday(&end, NULL); /* stop clock */

  long elapsed = (long)(end.tv_sec - start.tv_sec) * 1000
      + (((end.tv_usec - start.tv_usec) / 1000.0) + 0.5)
      - adjustment;

  printf("Integer divisions took %ldms\n", elapsed);

  gettimeofday(&start, NULL); /* start clock */

  for(i=0; i<ITERATIONS; ++i)
    decimal = fleft / fright;

  gettimeofday(&end, NULL); /* stop clock */

  elapsed = (long)(end.tv_sec - start.tv_sec) * 1000
      + (((end.tv_usec - start.tv_usec) / 1000.0) + 0.5)
      - adjustment;

  printf("Float divisions took %ldms\n", elapsed);

  return 0;
}
$ cc -ansi -pedantic -Wall -o divisionSpeedTest divisionSpeedTest.c &&
time ./divisionSpeedTest
divisionSpeedTest.c: In function `main':
divisionSpeedTest.c:25: warning: ISO C90 forbids mixed declarations and code
divisionSpeedTest.c:37: warning: ISO C90 forbids mixed declarations and code
Look with no work took 188ms
Integer divisions took 1081ms
Float divisions took 1163ms

real    0m2.810s
user    0m2.803s
sys     0m0.005s

$ cat divisionSpeedTest.pl
#!/usr/bin/perl

use Benchmark;
my($left, $right);

$left = 1;
$right = 2;

# Not even bothering to store the result
print timestr(timeit(100000000, "$left / $right"));

$ time ./divisionSpeedTest.pl

 2 wallclock secs ( 1.50 usr +  0.06 sys =  1.56 CPU) @ 64102564.10/s
(n=100000000)
real    0m46.252s
user    0m45.924s
sys     0m0.326s

****

I couldn't make head or tail out of the timeit() function's return
value, so I just used 'time' from the command line to see what really
happened.

So, the C code is roughly 20 times as fast. Ask any mathematician or
physicist who is running simulations which they would rather have. In C,
you can test for overflow if you want to. Can you disable this checking?
Can you disable runtime type determination? I'm guessing not. The price
C programmers pay is verbosity and the occasional surprising roundoff.

> And even then, if the expression was
> double d = 1.0 / 3;
> I would probably *still* not get a result with the precision afforded by
> the double d, and would have to write
> double d = 1.0D / 3;
> to get it right, right ?

Yup: float is the default decimal type. Double-precision takes longer,
so you have to ask for it.

$ cat precision.c
#include <stdio.h>

int main(int argc, char *argv[]) {
  float f = 1.0 / 7;
  float ff = (1.0f / 7.0f);
  double d = 1.0 / 7;
  double dd = 1.0 / 7;

  printf("%1.10e\n%1.10e\n%1.10e\n%1.10e\n", f, ff, d, dd);

  return 0;
}
cschultz@dev:~/projects/toys$ cc -ansi -pedantic -Wall -o precision
precision.c && ./precision
1.4285714924e-01
1.4285714924e-01
1.4285714286e-01
1.4285714286e-01

> I maintain what I said : a programming language which produces a wrong
> result, without even a warning, for an expression as simple as
> Long a = (1000 * 60 * 60 * 24 * 365)
> should be consigned to iPods and stuff, not used for serious things like
> Tomcat.

There are people using iPads for healthcare. One would expect that
precision counts, there, too. ;)

> Incidentally, and since you mentioned it :
> 
> C:\WINDOWS>perl
> print (1000 * 60 * 60 * 24 * 365);
> ^Z
> 31536000000

Of course! It just takes a few million more cycles to do it.

> That's what I call a smart and intuitive language..

Just not for the impatient.

- -chris
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (MingW32)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk02VjcACgkQ9CaO5/Lv0PDCOQCglqCp8wMh/69GrDErUL2loORl
H04An1lW8Pjq3xJnthVsDfQnuSAPyHmP
=2m1U
-----END PGP SIGNATURE-----

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Mime
View raw message