Question

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?

Était-ce utile?

La solution

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.

Autres conseils

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

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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top