Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

using (U)Int32 on 64-bit machines very awkward. #30123

Closed
Orbots opened this issue Nov 22, 2018 · 17 comments
Closed

using (U)Int32 on 64-bit machines very awkward. #30123

Orbots opened this issue Nov 22, 2018 · 17 comments

Comments

@Orbots
Copy link

Orbots commented Nov 22, 2018

I've read #9162 and understand the rational.

The awkwardness stems mostly from integer literals being 64 bit. For high performance real-time, memory limited or embedded systems you often do want to work with 32-bit integers. Games development is an example use-case. Additionally this type of code would tend to use a lot of integer literals. I've been doing things like x + UInt32(1) or defining constants like const three = UInt32(3) ugly. My code has UInt32 casts everywhere! At least with floats I can write 3.0f0. 0x00003 is not so nice.

I'd be happiest if integer literals could demote to match the other type(s) in a given operator, but I guess then your going to need a new type for literals or something. I can see this being complicated and fraught with peril.

Second happiest would be a more convenient way of entering 32-bit integer literals.

julia> typeof(UInt32(1) + 1)
Int64

Also why is that not UInt64? Seems inconsistent.

julia> typeof(UInt64(1) + 1)
UInt64
@Orbots
Copy link
Author

Orbots commented Nov 22, 2018

Just wanted to add that this conversion often leads to type instability in your performance conscious code if you aren't very careful.

@StefanKarpinski
Copy link
Member

StefanKarpinski commented Nov 22, 2018

The rule is that when integer arguments are of different types the result type is:

  • the type of the larger if the argument types are different sizes;
  • the unsigned type if they argument types are the same size.

This went through many iterations (#9162 is just the first of many). This simple rule is the end result of a lot of experimentation and we're fairly happy with it. We're also not able to change this until Julia 2.0. No rule works as desired in all circumstances; you're hitting one of the places where this rule is inconvenient. You may want to use typed local variables, e.g.:

function f()
    i::UInt32 = 123
    i += 1
    return i
end

Any assignment to i in this function body will be converted to UInt32.

@Orbots
Copy link
Author

Orbots commented Nov 22, 2018

how hard would it be to admit a simpler syntax for integer literals? C++ has 'l', 'L', 'u', 'U',[lLuU][lLuU] suffixes similar to 'f'. Although they all mean things bigger than 32 bits. 's' for signed int and 'u' for unsigned int?

Typed local variables help only a little.

The numerical programming community may be happy with this. However, I'd like to see Julia used in Game Development, so I wouldn't' have to code in C++ if I ever when back :P. Most people there would get really frustrated with the 64-bit literals. It's a pretty long stretch to get any game developer to accept a garbage collected language though, so I'd say this is a low priority. In my current job I can eat the ( in the limit ) 2x memory hit and no one will really notice.

Still 1u === UInt32(1) seems reasonable to me.

@yuyichao
Copy link
Contributor

how hard would it be to admit a simpler syntax for integer literals?

It's breaking since 1u is a valid syntax.

@Orbots
Copy link
Author

Orbots commented Nov 22, 2018

oh right. 2x = 2*x. '_u'? Or another letter if 'u' is taken or undesirable. C++ has a precedent for this kind of syntax. 1_u
https://en.cppreference.com/w/cpp/language/user_literal

@Orbots
Copy link
Author

Orbots commented Nov 22, 2018

or 1u0 could work. it's like 1f0

@yuyichao
Copy link
Contributor

yuyichao commented Nov 22, 2018

Those are currently valid syntaxes too. In fact, any suffix are valid, (including both 1u0 and 1_u). Note that I'm not saying it's impossible, I'm just saying it's breaking. Adding 1f0 would have been as breaking if it wasn't previously implemented.

@Orbots
Copy link
Author

Orbots commented Nov 22, 2018

Right. So 2.0 then :)

@Orbots
Copy link
Author

Orbots commented Nov 22, 2018

I'll close this, seems like a serious hassle for little gain right now.

@Orbots Orbots closed this as completed Nov 22, 2018
@KristofferC
Copy link
Member

KristofferC commented Nov 22, 2018

julia> struct U32 end

julia> Base.:*(n::Number, ::U32) = UInt32(n)

julia> const u = U32()
U32()

julia> 5u
0x00000005

heh. (don't name your variables u).

@DNF2
Copy link

DNF2 commented Nov 23, 2018

Would it be possible to
a) find a clever way to condense 0x00003, i.e. using prefix instead of postfix
b) a macro to change literals
c) something like setprecision
d) some Cassette magic
?

@KristofferC
Copy link
Member

@StefanKarpinski
Copy link
Member

@Orbots: you seem not to have noticed my suggestion of using typed local variables. Does that not address the problem?

@Orbots
Copy link
Author

Orbots commented Nov 23, 2018

@StefanKarpinski it partially addresses the problem of conveniently creating 32-bit literals. Does it address the problem of working with 32-bit ints being awkward in general? Not really.

julia> foo(x::UInt32) = x*x
foo (generic function with 1 method)

julia> foo(1)
ERROR: MethodError: no method matching foo(::Int64)  
Closest candidates are:
  foo(::UInt32) at REPL[1]:1  # **a little awkward** but error message is informative, so not too bad
Stacktrace:
 [1] top-level scope at none:0

julia> let
           two::UInt32 = 2  # this is ok
           foo( two + two )
         end
0x00000010   # **awkward**

julia> two::UInt32 = 2   
#**awkward**
ERROR: syntax: type declarations on global variables are not yet supported

julia> let
            two::UInt32 = 2
            foo( two +  3 ) 
   end
ERROR: MethodError: no method matching foo(::Int64)
 # oops, forgot to declare three... **a little awkward** but not horrible.

@Orbots
Copy link
Author

Orbots commented Nov 23, 2018

@KristofferC Looks promising. A little scary though :)
@StefanKarpinski this fixes one my awkward complaints:

import Base.show
show( io::IO, x::UInt32 ) = show(io, Int(x))
julia> two = UInt32(2)
2

Julia is flexible enough that I reckon I could work with 32 bit ints in a fairly natural manner via a custom package.

@StefanKarpinski
Copy link
Member

@Orbots, your example seems to be a matter of the function being too constrained in what argument types it accepts. This definition works fine:

julia> foo(x::Integer) = UInt32(x)^2
foo (generic function with 1 method)

julia> foo(1)
0x00000001

There are many other ways this can be handled as well.

@Orbots
Copy link
Author

Orbots commented Nov 26, 2018

@StefanKarpinski

There are many other ways this can be handled as well.

Exactly. UInt32 could be nicer to work with, but with minor effort it can be thanks to Julia's extremely extensible nature.

I don't want to get into this too much further right now. In the interest of "getting stuff done" I've opted to just use the most natural types Julia supports, Int and Float64 ( although I serialize to UInt32 and Float32 for sending over a socket). Another thing was that the performance of UInt32 vs Int was worse when I did a quick comparison. So I'd need to figure out what's going on there ( probably type stability in my code ). Anyways, that would be a separate issue if indeed I found a performance problem with UInt32, which I won't investigate right now.

Thanks for all all the suggestions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants