kumquat-quip opened a new issue, #1479:
URL: https://github.com/apache/arrow-go/issues/1479

   ### Describe the bug, including details regarding any error messages, 
version, and platform.
   
   Building a `Map` array from `ArrayData` (`array.NewMapData`, 
`array.MakeFromData`, and so any
   `Builder.NewArray()`) takes time exponential in the number of nested map 
levels. A single row
   of 20 nested maps takes about a second, and 24 levels take 16 s. The same 
nesting with `List`
   is instant.
   
   There are two causes in `arrow/array/map.go` (v18.8.0, unchanged on `main`). 
Each one builds or
   visits the map's item subtree twice per level:
   
   ```go
   func (a *Map) setData(data *Data) {
        a.validateData(data)
   
        a.List.setData(data)                                     // a.values = 
MakeFromData(entries struct),
                                                                 // which 
already builds keys and items
        a.keys = MakeFromData(data.childData[0].Children()[0])   // builds keys 
again
        a.items = MakeFromData(data.childData[0].Children()[1])  // builds 
items again: the whole nested subtree
   }
   
   func (a *Map) Retain() {
        a.List.Retain()   // List.Retain retains a.values (the entries Struct), 
and
                          // Struct.Retain retains its fields: keys and items
        a.keys.Retain()   // ...and then keys and items are retained again,
        a.items.Retain()  // recursing into the nested map a second time
   }
   // Release has the same shape.
   ```
   
   So construction is O(2^depth), and so is every `Retain`/`Release` of a 
nested map (the
   builders call these internally too).
   
   Reproduction (one row, one entry per level, `map<utf8, map<utf8, … int64>>`):
   
   ```go
   package main
   
   import (
        "fmt"
        "time"
   
        "github.com/apache/arrow-go/v18/arrow"
        "github.com/apache/arrow-go/v18/arrow/array"
        "github.com/apache/arrow-go/v18/arrow/memory"
   )
   
   func build(depth int, wrap func(arrow.DataType) arrow.DataType) arrow.Array {
        var dt arrow.DataType = arrow.PrimitiveTypes.Int64
        for i := 0; i < depth; i++ {
                dt = wrap(dt)
        }
        b := array.NewBuilder(memory.DefaultAllocator, dt)
        defer b.Release()
        var appendValue func(array.Builder)
        appendValue = func(b array.Builder) {
                switch b := b.(type) {
                case *array.MapBuilder:
                        b.Append(true)
                        b.KeyBuilder().(*array.StringBuilder).Append("k")
                        appendValue(b.ItemBuilder())
                case *array.ListBuilder:
                        b.Append(true)
                        appendValue(b.ValueBuilder())
                case *array.Int64Builder:
                        b.Append(1)
                }
        }
        appendValue(b)
        return b.NewArray()
   }
   
   func main() {
        mapOf := func(dt arrow.DataType) arrow.DataType { return 
arrow.MapOf(arrow.BinaryTypes.String, dt) }
        listOf := func(dt arrow.DataType) arrow.DataType { return 
arrow.ListOf(dt) }
        for _, depth := range []int{10, 15, 18, 20, 22, 24} {
                for _, c := range []struct {
                        name string
                        wrap func(arrow.DataType) arrow.DataType
                }{{"map", mapOf}, {"list", listOf}} {
                        start := time.Now()
                        arr := build(depth, c.wrap)
                        elapsed := time.Since(start)
                        arr.Release()
                        fmt.Printf("depth %2d %-4s %v\n", depth, c.name, 
elapsed)
                }
        }
   }
   ```
   
   Output (arrow-go v18.8.0, Go 1.27.3, Windows 11 amd64):
   
   ```
   depth 10 map  1.537ms
   depth 10 list 0s
   depth 15 map  33.2738ms
   depth 15 list 0s
   depth 18 map  269.1707ms
   depth 18 list 0s
   depth 20 map  1.0719985s
   depth 20 list 0s
   depth 22 map  4.2192563s
   depth 22 list 0s
   depth 24 map  16.2696208s
   depth 24 list 0s
   ```
   
   The time quadruples every two levels. Struct and list nesting stay linear. 
We hit this
   while adding Trino `MAP` → Arrow map support to the ADBC Trino driver: 
deeply nested MAP columns (20+
   levels) took seconds per batch to build.
   
   **Suggested fix:** reuse the children that `List.setData` already built, and 
let the list's
   reference counting cover them, since keys and items are owned by the entries 
struct:
   
   ```go
   func (a *Map) setData(data *Data) {
        a.validateData(data)
   
        a.List.setData(data)
        entries := a.values.(*Struct)
        a.keys = entries.Field(0)
        a.items = entries.Field(1)
   }
   
   func (a *Map) Retain()  { a.List.Retain() }
   func (a *Map) Release() { a.List.Release() }
   ```
   
   With this patch applied locally to v18.8.0, the reproduction is linear 
(under 1 ms at every
   depth up to 24), and `go test` passes for `./arrow/array/`, 
`./arrow/compute/...`,
   `./arrow/ipc/...`, `./arrow/cdata/...`, `./arrow/scalar/...` and 
`./arrow/avro/...`. A
   benchmark with nested maps would catch a regression.
   
   One side effect: `Keys()`/`Items()` become exactly the entries struct's 
fields, so they now
   honour an offset on the entries struct (as their doc comment already 
describes).
   
   ### Component(s)
   
   Other (`arrow/array`)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to