-
Melhore o uso da memória e o desempenho com o Swift
Descubra maneiras de melhorar o desempenho e o gerenciamento de memória do seu código Swift. Vamos explorar maneiras de aprimorar seu código, desde alterações algorítmicas avançadas até o uso dos novos tipos de InlineArray e Span, que oferecem um controle mais preciso sobre a memória e as alocações.
Capítulos
- 0:00 - Introdução
- 1:19 - App: Formato QOI e parser
- 2:25 - Algoritmos
- 8:17 - Alocações
- 16:30 - Exclusividade
- 19:12 - Pilha x heap
- 21:08 - Contagem de referências
- 29:52 - Biblioteca de análise binária do Swift
- 31:03 - Próximas etapas
Recursos
Vídeos relacionados
WWDC25
- Monitore e otimize o consumo de energia do seu app
- Novidades do Swift
- Otimize o desempenho da CPU com o Instruments
WWDC24
-
Buscar neste vídeo...
-
-
7:01 - Corrected Data.readByte() method
import Foundation extension Data { /// Consume a single byte from the start of this data. mutating func readByte() -> UInt8? { guard !isEmpty else { return nil } return self.popFirst() } } -
9:56 - RGBAPixel.data(channels:) method
extension RGBAPixel { /// Returns the RGB or RGBA values for this pixel, as specified /// by the given channels information. func data(channels: QOI.Channels) -> some Collection<UInt8> { switch channels { case .rgb: [r, g, b] case .rgba: [r, g, b, a] } } } -
10:21 - Original QOIParser.parseQOI(from:) method
extension QOIParser { /// Parses an image from the given QOI data. func parseQOI(from input: inout Data) -> QOI? { guard let header = QOI.Header(parsing: &input) else { return nil } let pixels = readEncodedPixels(from: &input) .flatMap { decodePixels(from: $0) } .prefix(header.pixelCount) .flatMap { $0.data(channels: header.channels) } return QOI(header: header, data: Data(pixels)) } } -
12:53 - Revised QOIParser.parseQOI(from:) method
extension QOIParser { /// Parses an image from the given QOI data. func parseQOI(from input: inout Data) -> QOI? { guard let header = QOI.Header(parsing: &input) else { return nil } let totalBytes = header.pixelCount * Int(header.channels.rawValue) var pixelData = Data(repeating: 0, count: totalBytes) var offset = 0 while offset < totalBytes { guard let nextPixel = parsePixel(from: &input) else { break } switch nextPixel { case .run(let count): for _ in 0..<count { state.previousPixel .write(to: &pixelData, at: &offset, channels: header.channels) } default: decodeSinglePixel(from: nextPixel) .write(to: &pixelData, at: &offset, channels: header.channels) } } return QOI(header: header, data: pixelData) } } -
15:07 - Array behavior
var array = [1, 2, 3] array.append(4) array.removeFirst() // array == [2, 3, 4] var copy = array copy[0] = 10 // copy happens on mutation // array == [2, 3, 4] // copy == [10, 3, 4] -
19:47 - InlineArray behavior (part 1)
var array: InlineArray<3, Int> = [1, 2, 3] array[0] = 4 // array == [4, 2, 3] // Can't append or remove elements array.append(4) // error: Value of type 'InlineArray<3, Int>' has no member 'append' // Can only assign to a same-sized inline array let bigger: InlineArray<6, Int> = array // error: Cannot assign value of type 'InlineArray<3, Int>' to type 'InlineArray<6, Int>' -
20:23 - InlineArray behavior (part 2)
var array: InlineArray<3, Int> = [1, 2, 3] array[0] = 4 // array == [4, 2, 3] var copy = array // copy happens on assignment for i in copy.indices { copy[i] += 10 } // array == [4, 2, 3] // copy == [14, 12, 13] -
23:13 - processUsingBuffer() function
// Safe usage of a buffer pointer func processUsingBuffer(_ array: [Int]) -> Int { array.withUnsafeBufferPointer { buffer in var result = 0 for i in 0..<buffer.count { result += calculate(using: buffer, at: i) } return result } } -
23:34 - Dangerous getPointerToBytes() function
// Dangerous - DO NOT USE! func getPointerToBytes() -> UnsafePointer<UInt8> { let array: [UInt8] = Array(repeating: 0, count: 128) // DANGER: The next line escapes a pointer let pointer = array.withUnsafeBufferPointer { $0.baseAddress! } // DANGER: The next line returns the escaped pointer return pointer } -
24:46 - processUsingSpan() function
// Safe usage of a span @available(macOS 16.0, *) func processUsingSpan(_ array: [Int]) -> Int { let intSpan = array.span var result = 0 for i in 0..<intSpan.count { result += calculate(using: intSpan, at: i) } return result } -
25:07 - getHiddenSpanOfBytes() function (attempt 1)
@available(macOS 16.0, *) func getHiddenSpanOfBytes() -> Span<UInt8> { } // error: Cannot infer lifetime dependence... -
25:28 - getHiddenSpanOfBytes() function (attempt 2)
@available(macOS 16.0, *) func getHiddenSpanOfBytes() -> () -> Int { let array: [UInt8] = Array(repeating: 0, count: 128) let span = array.span return { span.count } } -
26:27 - RawSpan.readByte() method
@available(macOS 16.0, *) extension RawSpan { mutating func readByte() -> UInt8? { guard !isEmpty else { return nil } let value = unsafeLoadUnaligned(as: UInt8.self) self = self._extracting(droppingFirst: 1) return value } } -
28:02 - Final QOIParser.parseQOI(from:) method
/// Parses an image from the given QOI data. mutating func parseQOI(from input: inout RawSpan) -> QOI? { guard let header = QOI.Header(parsing: &input) else { return nil } let totalBytes = header.pixelCount * Int(header.channels.rawValue) let pixelData = Data(rawCapacity: totalBytes) { outputSpan in while outputSpan.count < totalBytes { guard let nextPixel = parsePixel(from: &input) else { break } switch nextPixel { case .run(let count): for _ in 0..<count { previousPixel .write(to: &outputSpan, channels: header.channels) } default: decodeSinglePixel(from: nextPixel) .write(to: &outputSpan, channels: header.channels) } } } return QOI(header: header, data: pixelData) } -
28:31 - RGBAPixel.write(to:channels:) method
@available(macOS 16.0, *) extension RGBAPixel { /// Writes this pixel's RGB or RGBA data into the given output span. @lifetime(&output) func write(to output: inout OutputRawSpan, channels: QOI.Channels) { output.append(r) output.append(g) output.append(b) if channels == .rgba { output.append(a) } } }
-
-
- 0:00 - Introdução
Aprenda a otimizar o desempenho de apps e bibliotecas em Swift usando o Swift 6.2. Os novos tipos 'InlineArray' e 'Span' reduzem alocações, verificações de exclusividade e contagem de referências. Uma nova biblioteca de código aberto para Swift, Binary Parsing, foi lançada para análise binária rápida e segura.
- 1:19 - App: Formato QOI e parser
O app desta sessão da WWDC25 carrega imagens no formato QOI, formato sem perdas com especificação de página única. O parser de imagens aceita vários métodos de codificação de pixels. O app carrega instantaneamente um ícone pequeno, mas demora alguns segundos para carregar uma foto maior.
- 2:25 - Algoritmos
Quando apps lidam com dados reais, problemas de desempenho podem ocorrer devido ao uso incorreto de algoritmos ou estruturas de dados. Para identificar e resolver esses problemas, use o Instruments, que tem modelos para analisar alocações e liberações, além de identificar código ineficiente com criadores de perfil. O instrumento Time Profiler é útil para identificar problemas de desempenho. Ao analisar as chamadas capturadas e as pilhas de execução, identifique as áreas onde os apps gastam mais tempo. Uma parte do tempo foi gasta em uma chamada de sistema para copiar dados, o 'platform_memmove'. Usando o Instruments, este exemplo analisa um método personalizado chamado 'readByte'. Esse método foi adicionado a uma extensão do tipo 'Data', o que causou cópias excessivas dos dados binários. No exemplo, o método foi substituído pelo mais eficiente 'popFirst()', que reduz os dados do início de uma sequência sem realizar cópias. Essa alteração resolveu o problema de desempenho no método 'readByte'. Após a alteração, o exemplo executou a análise de desempenho novamente, e a barra significativa do 'platform_memmove' desapareceu do gráfico de chamas. Os testes de desempenho mostraram aumento na velocidade, e a relação entre o tamanho da imagem e o tempo de análise mudou de quadrática para linear, com um algoritmo mais eficiente.
- 8:17 - Alocações
O app foi novamente analisado com ferramentas de profiling e constatou-se que o parser de imagens causa alocações e desalocações excessivas de memória envolvendo matrizes. O alto número de alocações, quase um milhão para analisar uma única imagem, indica um problema crítico. A maioria dessas alocações é transitória, de curta duração, podendo ser otimizada. Para identificar a origem dessas alocações desnecessárias, o exemplo utiliza o instrumento Allocations no Instruments. A análise revela que um método chamado 'RGBAPixel.data(channels:)' é o principal responsável. Esse método cria uma matriz toda vez que é chamada, causando um número substancial de alocações. A estrutura do código, que envolve uma cadeia complexa de métodos 'flatMap' e 'prefix', contribui para o problema. Cada etapa dessa cadeia gera novas alocações, pois as matrizes são criadas, niveladas e copiadas repetidamente. Embora a abordagem seja concisa, não é eficiente em termos de memória. Para resolver esse problema, o exemplo reescreve a função de análise. Em vez de depender de alocações intermediárias, ela calcula o tamanho total dos dados resultantes e aloca um único buffer. Essa abordagem elimina a necessidade de alocações repetidas durante o processo de decodificação.
- 16:30 - Exclusividade
O desempenho do app melhorou tanto que os instrumentos de profiling precisaram de mais dados. Após executar o código de análise 50 vezes, os resultados mostraram os 'swift_beginAccess' e 'swift_endAccess', que indicam testes de exclusividade. Esses testes de exclusividade foram causados por propriedades na classe 'State', aninhada dentro do 'QOIParser'. O exemplo move essas propriedades para o tipo pai do parser para eliminar os testes de exclusividade. Após ajustes no compilador, a verificação de exclusividade foi eliminada, conforme verificado em uma nova execução de análise de desempenho.
- 19:12 - Pilha x heap
O exemplo substitui o uso de 'Array' pelo 'InlineArray', uma coleção de tamanho fixo armazenada em linha, que otimiza o uso de memória eliminando a contagem de referências e os testes de exclusividade. É ideal para o cache de pixels, uma matriz de 64 elementos que não muda de tamanho e é modificada no lugar, melhorando o desempenho sem copiar ou compartilhar referências.
- 21:08 - Contagem de referências
No exemplo de otimização do app, é usado o novo tipo 'Span' para melhorar o desempenho e aumentar a segurança da memória. No Instruments, o gráfico de chamas é usado na análise do Time Profiler. A criação de perfis de dados foca o 'QOIParser' e revela que parte do tempo é gasta em operações de contagem de referências no tipo 'Data', devido à sua semântica de copy-on-write. 'Span' e seus tipos relacionados são uma nova forma de trabalhar com memória contígua em uma coleção. Eles usam o recurso não escapável ('~Escapable') do Swift, que vincula o tempo de vida à coleção, garantindo segurança de memória e eliminando a necessidade de gerenciamento manual. Isso permite o acesso eficiente à memória sem os riscos associados a ponteiros não seguros. O exemplo demonstra como usar os tipos 'Span' para reescrever métodos existentes, tornando-os mais simples, seguros e eficientes. Nos métodos de análise de imagem, 'Data' é substituído por 'RawSpan', reduzindo a sobrecarga da contagem de referências. Além disso, o 'OutputSpan' é adotado no processo de análise para otimização adicional, tornando a operação seis vezes mais rápida, sem recorrer a ponteiros não seguros.
- 29:52 - Biblioteca de análise binária do Swift
O Swift Binary Parsing permite criar parsers seguros e eficientes para formatos binários. Ele oferece ferramentas para lidar com aspectos de segurança, incluindo prevenção de estouro de inteiros, especificação de sinalização, largura de bits e ordem dos bytes. A biblioteca já é usada pela Apple e está disponível para você experimentar e contribuir por meio dos fóruns Swift e do GitHub.
- 31:03 - Próximas etapas
Os principais pontos incluem: Uso do Xcode e Instruments para analisar o desempenho de apps. Análise do desempenho de algoritmos para identificar gargalos. Exploração de soluções para os problemas acima com os novos tipos 'InlineArray' e 'Span' lançados no Swift 6.2.