Holy smoky, this loop flies now! Thanks so much to everyone who contributed 
to help, awesome feedback.

Following are a few comments from my further tests with enclosed my current 
fastest version:

   - In my script on the global scope, I can define a type, but I cannot 
   defined it const.  So:
      - GlobalIdType = Int works but
      - const GlobalIdType = Int errors out.
   - I can define a type const within a function definition, but it does 
   not seem to have any performance benefit.
   - As Stefan explained, the performance hit with my initial integer 
   increment i += 1 was not because it was not typed but because it was not 
   considered a constant type by the compiler.  I decoupled my array index 
   types with their column id types, declaring that array index type inside my 
   function definition, and those increments pretty much disappeared from the 
   profile report. I did not have to declare explicitly those index types, the 
   default Int would have worked just fine, I was just experimenting to make 
   sure I did not misunderstand anything.
   - Then, again using Stefan's hint, the next bottleneck was the array 
   accesses.  I added the types of interest as parameters of my function 
   (i.e., template function in the C++ world), annotated the array types 
   accordingly, and here we go, another order of magnitude of performance 
   gain!  Using @code_typed shows all variables are typed, no Any shows up.
   - Now, the slowest part is no longer the loop being iterated over about 
   100,000 times in my test, but the initial array allocation of the results 
   being returned by the function -- quite amazing the efficiency of the core 
   iteration!

This was a very educational exercise for me, this will help me write code 
appropriately typed and fast from the beginning.

I have a *remaining question* on how to best pass those types in a function 
signature where my main data container is a DataFrame.

Essentially, I functionally have:

function foo(dt::DataFrame)
>   a = dt[:col].data # An array as column of my DataFrame, data with no NA.
>   const MyDataType = eltype(a)
>   ...
> end

 
As specified above, access is slow because the array "a" if of type 
Array{Any} at compile time, so not optimal by an order of magnitude.

What I essentially did to type and hence speed up the array access was 
passing the array data type parameter as follows:

>
> function foo(dt::DataFrame, dummyParameter::MyDataType)

  a = dt[:col].data::Array{MyDataType} # An array as column of my 
> DataFrame, data with no NA.
>   ...
> end

 
 But it is not clean, the data type "MyDataType" is redundant as embedded 
in the data frame parameter "dt".
Is there a way to annotate directly the data frame parameter "dt" with its 
column types so I don't have to create a dummy function parameter for this 
purpose?
If so, how?
If my data frame has N columns with my data type of interest in its first 
column, I am looking for something conceptually like below (syntax below 
does not work):

function foo(dt::*DataFrame{**MyDataType, ...}*)

  a = dt[:col].data::Array{MyDataType} # An array as column of my 
> DataFrame, data with no NA.
>   ...
> end


Note that the following pattern does not solve the array type inference 
problem (i.e., it is slow), see Generic performance problems, #523 by John 
Myles White <https://github.com/JuliaStats/DataFrames.jl/issues/523> (where 
I initially learned to go access the data array directly for optimal 
performance):

>
> function foo(dt::DataFrame)
>   da = dt[:col]
>   const MyDataType = eltype(a)
>   a = dt[:col].data::Array{MyDataType} # An array as column of my 
> DataFrame, data with no NA.

  ...
> end

 
(Perhaps I should ask this question in a different post as it is beyond the 
scope of my initial increment performance question)

Thanks much. 

On Thursday, September 18, 2014 4:25:25 PM UTC-4, Stefan Karpinski wrote:
>
> On Thu, Sep 18, 2014 at 2:33 PM, Ivar Nesje <iva...@gmail.com 
> <javascript:>> wrote:
>
>> Seems like Julia is not smart enough to guess that i::IdType will always 
>> ensure that i is a Int64 when IdType might change.
>
>
> Since IdType is not constant, it can change at any time – generating code 
> on the premise that it cannot change would be incorrect. Instead, the code 
> generated for this function needs to handle the possibility that IdType can 
> change at any point, which basically makes all optimizations impossible. 
> It's a bit low level (we should really expose this better), but you can use 
> @code_typed to see what the inferred types of all the local variables are:
>
> julia> (@code_typed manual_iter1(1,2))[1].args[2][2]
> 38-element Array{Any,1}:
>  {:dt1,Int64,0}
>  {:dt2,Int64,0}
>  {:zeroIdType,Any,18}
>  {:oneIdType,Any,18}
>  {:zeroDType,Any,18}
>  {symbol("#s5149"),Any,18}
>  {symbol("#s5150"),Any,18}
>  {:dt1id1,Any,18}
>  {:dt1D,Any,18}
>  {symbol("#s5151"),Any,18}
>  {symbol("#s5152"),Any,18}
>  {symbol("#s5153"),Any,18}
>  {:dt2id2,Any,18}
>  {:dt2D1,Any,18}
>  {:dt2D2,Any,18}
>  {:MAX_INT,Any,18}
>  {:MAX_D,Any,18}
>  {:nrow1,Any,18}
>  {:nrow2,Any,18}
>  {:i1,Any,2}
>  {:i1id1,Any,2}
>  {:i1D,Any,2}
>  {:i2Lower,Any,2}
>  {:i2LowerD2,Any,2}
>  {:i2Upper,Any,2}
>  {:i2UpperD1,Any,2}
>  {:i2UpperD2,Any,2}
>  {:i2Match,Any,2}
>  {:i,Any,2}
>  {:nrowMax,Any,18}
>  {:resid1,Any,18}
>  {:resid2,Any,18}
>  {:resD1,Any,18}
>  {:resD,Any,18}
>  {:resD2,Any,18}
>  {:i2MatchD2,Any,18}
>  {:i2MatchD1,Any,18}
>  {symbol("#s5154"),Any,18}
>
>
> As you can see, it's not pretty: given Int arguments, literally only the 
> types of the arguments themselves are more specific than Any. If you 
> declare IdType and DType to be const, it gets better, but still not ideal:
>
> julia> (@code_typed manual_iter1(1,2))[1].args[2][2]
> 38-element Array{Any,1}:
>  {:dt1,Int64,0}
>  {:dt2,Int64,0}
>  {:zeroIdType,Int64,18}
>  {:oneIdType,Int64,18}
>  {:zeroDType,Int64,18}
>  {symbol("#s122"),Any,18}
>  {symbol("#s121"),Any,18}
>  {:dt1id1,Any,18}
>  {:dt1D,Any,18}
>  {symbol("#s120"),Any,18}
>  {symbol("#s119"),Any,18}
>  {symbol("#s118"),Any,18}
>  {:dt2id2,Any,18}
>  {:dt2D1,Any,18}
>  {:dt2D2,Any,18}
>  {:MAX_INT,Int64,18}
>  {:MAX_D,Int64,18}
>  {:nrow1,Int64,18}
>  {:nrow2,Int64,18}
>  {:i1,Int64,2}
>  {:i1id1,Int64,2}
>  {:i1D,Int64,2}
>  {:i2Lower,Int64,2}
>  {:i2LowerD2,Int64,2}
>  {:i2Upper,Int64,2}
>  {:i2UpperD1,Int64,2}
>  {:i2UpperD2,Int64,2}
>  {:i2Match,Int64,2}
>  {:i,Int64,2}
>  {:nrowMax,Int64,18}
>  {:resid1,Any,18}
>  {:resid2,Any,18}
>  {:resD1,Any,18}
>  {:resD,Any,18}
>  {:resD2,Any,18}
>  {:i2MatchD2,Any,18}
>  {:i2MatchD1,Any,18}
>  {symbol("#s117"),Any,18}
>
>
> When you pull something out of an untyped structure like dt1id1, dt1D, 
> etc. it is a good idea to provide some type annotations if you can. Most of 
> the other type annotations inside the function body are unnecessary, 
> however.
>
>

Attachment: crossJoinFilter.jl
Description: Binary data

Reply via email to