1

I wanted to show to a colleague that you can allocate more than 2GB of ram, so I made a little test application.

let mega = 1 <<< 20
let konst x y = x
let allocate1MB _ = Array.init mega (konst 0uy)
let memoryHog = Array.Parallel.init 8192 allocate1MB

printfn "I'm done..."
System.Console.ReadKey() |> ignore

this works and you actually see the process happily hogging away at the system's memory. However, it takes somewhat long - hence the Array.Parallel.init.

I noticed, that the same code does not work, if I write it with

let allocate1MB _ = Array.zeroCreate mega

More precisely, no data is allocated and it takes no time.

So thus my question; What is the difference in semantics between Array.zeroCreate and Array.init?

I understand that Array.init would run my konst 0uy function each time, which would explain the time difference. But why does Array.zeroCreate not allocate the memory?

Guy Coder
  • 24,501
  • 8
  • 71
  • 136
Daniel Fabian
  • 3,828
  • 2
  • 19
  • 28

3 Answers3

2

From the FSharp.Core sources:

let inline init (count:int) (f: int -> 'T) = 
    if count < 0 then invalidArg "count" InputMustBeNonNegativeString
    let arr = (zeroCreateUnchecked count : 'T array)  
    for i = 0 to count - 1 do 
        arr.[i] <- f i
    arr

let zeroCreate count = 
    if count < 0 then invalidArg "count" (SR.GetString(SR.inputMustBeNonNegative))
    Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked count

let inline zeroCreateUnchecked (count:int) = 
    (# "newarr !0" type ('T) count : 'T array #)

As you can see, the both functions use zeroCreateUnchecked under the hood, which is compiled to newarr IL that is supposed to push a new array onto the stack.

The fact that this does not consume any memory in your case might indicate that some kind of optimization is responsible. I think either JIT or the compiler is removing this operation since the created array is never used, and that obviously does not happen in the case of Array.init.

MisterMetaphor
  • 5,900
  • 3
  • 24
  • 31
  • Thx for the info. After playing around with it a bit, I found, that it actually seems to only allocate the memory upon access. Not even writing to a specific array element was sufficient to allocate all of the memory. Only when I say used Array.sum, it would actually allocate the RAM. Very interesting JIT voodoo going on here. – Daniel Fabian Oct 08 '13 at 10:36
0

Array.zeroCreate does allocate the memory, it will create the array with each item initialized to the default value, whereas Array.init enables you to set the value of each item.

e.g.

// create large array of bytes set to 0
let array : byte[] = Array.zeroCreate (1 <<< 20)

// create large array of object references set to null.
let array : obj[] = Array.zeroCreate (1 <<< 20)

Changing these 2 lines in your example will throw Out of Memory exceptions when I run it, so memory is being allocated.

let allocate1MB _ = Array.zeroCreate mega
let memoryHog : byte[][] = Array.Parallel.init 8192 allocate1MB
Leaf Garland
  • 3,647
  • 18
  • 18
0

Answering your direct question: Array.zeroCreate makes array of elements of type's default value while Array.init makes array of elements using a provided generator function to create each. You can always implement semantics of Array.zeroCreate by using a correspondent generator function of Array.init:

-for a value type, e.g. byte:

> let az: byte [] = Array.zeroCreate 1;;
val az : byte [] = [|0uy|]
> let ai = Array.init 1 (fun _ -> 0uy);;
val ai : byte [] = [|0uy|]

-for a reference type, e.g. string:

> let az: string [] = Array.zeroCreate 1;;
val az : string [] = [|null|]
> let ai = Array.init 1 (fun _ -> Unchecked.defaultof<string>);;
val ai : string [] = [|null|]

Now, applying this observation to the original problem of allocating more, than 2GB of RAM, you can do such allocation using Array.zeroCreate under .NET 4.5 and 64-bit OS after enabling gcAllowVeryLargeObjects in configuration of fsiAnyCPU.exe. The single line of code below allocates almost 8GB of RAM for an array of int in no time:

> let bigOne: int [] = Array.zeroCreate 2146435071

And the following proves that this has worked:

> bigOne.[2146435070] <- 1
val it : unit = ()
> bigOne.[2146435070]
val it : int = 1
Gene Belitski
  • 10,270
  • 1
  • 34
  • 54