Код Swift работает в 2.5x медленее

swift

#1

Есть задача написать на swift код для определения размера зон одного цвета на картинке.
Написал сначала на ObjectiveC, потом перевел на Swift но получил падение скорости обработки больше чем в 10x.

После всех оптимизаций получилось уменьшить разрыв до 2.5x но этого не достаточно.

  1. Картинка: градиент от (0,0,0) до (255,0,0) с размером 2048x2048.

  2. Код Swift:
    @IBAction func allZonesOpt(_ sender: Any) {
    let startDate = Date()

     let image : UIImage = UIImage(named:"idx.png")!
     
     let cgImage : CGImage = image.cgImage!
     guard let pixelData = CGDataProvider(data: (cgImage.dataProvider?.data)!)?.data else {
         return
     }
     let data = CFDataGetBytePtr(pixelData)!
     let numberOfComponents:Int = 4
     
     let imageWidth = Int(image.size.width)
     let imageHeight = Int(image.size.height)
     let bytesPerRow = imageWidth * numberOfComponents
     
     
     let rangeOne = 0..<Int(image.size.height)
     let rangeTwo = 0..<Int(image.size.width)
     var arrayWithZones = [(Int, Int)]()
     
     for colorRed:UInt8 in 0...255 {
             let colorR = colorRed
             let colorG = 0
             let colorB = 0
         
         
             var minx:Int = imageWidth
             var miny:Int = imageHeight
             var maxx:Int = 0
             var maxy:Int = 0
             let startDateFor = Date()
         for y in rangeOne {
             for x in rangeTwo {
                     let pixelData = (bytesPerRow * y) + x * numberOfComponents
                     
                     if data[pixelData] == colorR && data[pixelData + 1] == colorG && data[pixelData+2] == colorB{
                         if minx > x{
                             minx = x
                         }
                         if miny > y{
                             miny = y
                         }
                         if maxx < x{
                             maxx = x
                         }
                         if maxy < y{
                             maxy = y
                         }
                     }
                 }
             }
             let methodFinishFor = Date()
             let timeIntervalFor = methodFinishFor.timeIntervalSince(startDateFor)
             print(" time execution = \(timeIntervalFor)")
             arrayWithZones.append((maxx - minx, maxy - miny))
     }
     
     
     let methodFinish = Date()
     let timeInterval = methodFinish.timeIntervalSince(startDate)
     resultLabel.text = " time execution = \(timeInterval)"
     print(" time execution = \(timeInterval)")
     print("atFinal = \(arrayWithZones)");
    

    }

  3. Код ObjectiveC

  • (IBAction)allZonesAsSwift:(id)sender {
    NSDate *methodStart = [NSDate date];
UIImage *image = [UIImage imageNamed:@"idx.png"];

//static rawData
CGImageRef imageRef = [image CGImage];
unsigned int width = (unsigned int)CGImageGetWidth(imageRef);
unsigned int height = (unsigned int)CGImageGetHeight(imageRef);

int bytesPerPixel = 4;
int bytesPerRow = bytesPerPixel * width;

CFDataRef data = CGDataProviderCopyData(CGImageGetDataProvider(imageRef));
const unsigned char * rawData =  CFDataGetBytePtr(data);

unsigned int minx = image.size.width;
unsigned int miny = image.size.height;
unsigned int maxx = 0;
unsigned int maxy = 0;

unsigned char colorR = 0;
unsigned char colorG = 0;
unsigned char colorB = 0;

NSMutableArray *arrayWithZones = [NSMutableArray<NSValue*> new];

for (int colorRed = 0; colorRed < 256; colorRed ++) {
    colorR = (unsigned char)colorRed;
    
    minx = (unsigned  int)image.size.width;
    miny = (unsigned int)image.size.height;
    maxx = 0;
    maxy = 0;
    NSDate *methodStartFor = [NSDate date];
    for (unsigned int y = height - 1; y; y--) {
        for (unsigned int x = width - 1; x; x--) {
            unsigned int byteIndex = (bytesPerRow * y) + x * bytesPerPixel;
           
            if (rawData[byteIndex] == colorR && rawData[byteIndex + 1] == colorG && rawData[byteIndex + 2] == colorB) {
                minx = x < minx ? x : minx;
                maxx = x > maxx ? x : maxx;
                miny = y < miny ? y : miny;
                maxy = y > maxy ? y : maxy;
            }
        }
    }
    
    NSDate *methodFinishFor = [NSDate date];
    NSTimeInterval executionTimeFor = [methodFinishFor timeIntervalSinceDate:methodStartFor];
    NSLog(@"executionTime Zone = %f", executionTimeFor);
    
    CGSize final = CGSizeMake(maxx - minx, maxy - miny);
    [arrayWithZones addObject:[NSValue valueWithCGSize:final]];
}

free(rawData);

NSDate *methodFinish = [NSDate date];
NSTimeInterval executionTime = [methodFinish timeIntervalSinceDate:methodStart];
NSLog(@"color picker = %f", executionTime);
_resultLabel.text = [NSString stringWithFormat:@"color picker = %f", executionTime];
NSLog(@"final = %@", arrayWithZones);

}


#2

Пригласил в тему @artFintch, специалиста по алгоритмам.


#3

Привет!
Я не специалист по алгоритмам))

По коду вроде всё ок, разве что тут кажется перемудрил:

guard let pixelData = cgImage.dataProvider?.data else {
Но это не влияет на асимптотику алгоритма.

Профилировщиком посмотрел, самое медленное в коде на Swift - это сравнение. Идей по оптимизации самого кода у меня нет, к сожалению. Думаю вся проблема в том, что ты сравниваешь в лоб попиксельно. Можно поискать более сложные алгоритмы, наверняка там используется быстрое преобразование Фурье и тп. для сжатия изображения и более быстрого сравнения.

Из мелочей, могу посоветовать тестировать код в тестах с помощью measure { ... }. Это удобнее, не надо даты самому писать. Также вывод в консоль замедляет тест. А measure выполняет сразу несколько прогонов.


#4

Спасибо за ответ, все таки сделаю эту часть на ObjectiveC.
Проблему с Swift никто не смог решить и это не стоит затрат.