Uploaded image for project: 'Swift'
  1. Swift
  2. SR-12125

String(decoding:from:) doesn't try `withContiguousStorageIfAvailable

    XMLWordPrintable

    Details

    • Type: Bug
    • Status: Open
    • Priority: Medium
    • Resolution: Unresolved
    • Component/s: Standard Library
    • Labels:
    • Environment:

      swift 5.1 and 5.2 branch

      Description

      String(decoding:from does sometimes make intermediate Array copies of the Collection that it receives. That makes sense because the collection might not be contiguous. If however the collection implements withContiguousStorageIfAvailable, then String(decoding:from should use this.

      The attached program shows this (parts reproduced here):

      struct MyCollection: Collection {
          let storage: [UInt8]
          typealias Element = UInt8
          typealias Index = Array<UInt8>.Index
          typealias SubSequence = Array<UInt8>.SubSequence
          typealias Indices = Array<UInt8>.Indices
      
          init(underlying: [UInt8]) {
              self.storage = underlying
          }
      
          public var indices: Indices {
              return self.storage.indices
          }
          public subscript(bounds: Range<Index>) -> SubSequence {
              return self.storage[bounds]
          }
      
          public subscript(position: Index) -> Element {
              return self.storage[position]
          }
      
          public var startIndex: Index {
              return self.storage.startIndex
          }
      
          public var endIndex: Index {
              return self.storage.endIndex
          }
      
          func index(after i: Index) -> Index {
              return self.storage.index(after: i)
          }
      
          func withContiguousStorageIfAvailable<R>(_ body: (UnsafeBufferPointer<UInt8>) throws -> R) rethrows -> R? {
              return try self.storage.withUnsafeBufferPointer(body)
          }
      }
      
      func makeString<Bytes: Collection>(_ bytes: Bytes) -> String where Bytes.Element == UInt8 {
          return String(decoding: bytes, as: Unicode.UTF8.self)
      }
      
      func testMyCollectionUnsafe(_ array: MyCollection) -> String {
          return array.withContiguousStorageIfAvailable {
              return makeString($0)
          }!
      }
      
      func testMyCollection(_ array: MyCollection) -> String {
          return makeString(array)
      }
      
      @inline(never)
      func doMyCollectionUnsafe() {
          let array: [UInt8] = Array(repeating: UInt8(ascii: "X"), count: 15)
          let myCollection = MyCollection(underlying: array)
      
          for _ in 0..<10000 {
              precondition(testMyCollectionUnsafe(myCollection) == "XXXXXXXXXXXXXXX")
          }
      }
      
      @inline(never)
      func doMyCollection() {
          let array: [UInt8] = Array(repeating: UInt8(ascii: "X"), count: 15)
          let myCollection = MyCollection(underlying: array)
      
          for _ in 0..<10000 {
              precondition(testMyCollection(myCollection) == "XXXXXXXXXXXXXXX")
          }
      }
      
      doMyCollection()
      doMyCollectionUnsafe()
      

      If we look at all allocations that happen more than once we see only

                    libsystem_malloc.dylib`malloc
                    libswiftCore.dylib`swift_slowAlloc+0x19
                    libswiftCore.dylib`swift_allocObject+0x27
                    test`specialized _ContiguousArrayBuffer.init(_uninitializedCount:minimumCapacity:)+0x39
                    test`specialized Collection._copyToContiguousArray()+0x4f
                    test`specialized makeString<A>(_:)+0x11a
                    test`doMyCollection()+0xb2
                    test`main+0x13
                    libdyld.dylib`start+0x1
                    test`0x1
                  10000
      

      so doMyCollection allocates 10,000 times but doMyCollectionUnsafe does not.

      repro

      swiftc -O test.swift && sudo ~/devel/swift-nio/dev/malloc-aggregation.d -c ./test
      

        Attachments

          Activity

            People

            Assignee:
            milseman Michael Ilseman
            Reporter:
            jw Johannes Weiss
            Votes:
            1 Vote for this issue
            Watchers:
            5 Start watching this issue

              Dates

              Created:
              Updated: