QML 성능 고려 사항 및 제안 사항

타이밍 고려 사항

애플리케이션 개발자는 렌더링 엔진이 초당 60프레임의 일관된 새로 고침 빈도를 유지할 수 있도록 노력해야 합니다. 60 FPS는 각 프레임 사이에 약 16밀리초의 처리 시간이 있으며, 여기에는 그래픽 하드웨어에 드로잉 프리미티브를 업로드하는 데 필요한 처리 시간이 포함됩니다.

실제로 이는 애플리케이션 개발자가

  • 가능한 경우 비동기 이벤트 중심 프로그래밍을 사용합니다.
  • 작업자 스레드를 사용하여 중요한 처리를 수행합니다.
  • 이벤트 루프를 수동으로 돌리지 않습니다.
  • 블로킹 함수 내에서 프레임당 몇 밀리초 이상을 사용하지 않습니다.

그렇게 하지 않으면 프레임을 건너뛰게 되어 사용자 경험에 큰 영향을 미칩니다.

참고: 유혹적이지만 절대 사용해서는 안 되는 패턴은 QML에서 호출된 C++ 코드 블록 내에서 블로킹을 피하기 위해 자체적으로 QEventLoop 을 만들거나 QCoreApplication::processEvents()을 호출하는 것입니다. 이는 신호 처리기나 바인딩에 이벤트 루프가 입력되면 QML 엔진이 다른 바인딩, 애니메이션, 트랜지션 등을 계속 실행하기 때문에 위험합니다. 이러한 바인딩은 예를 들어 이벤트 루프가 포함된 계층 구조를 파괴하는 부작용을 일으킬 수 있습니다.

프로파일링

가장 중요한 팁은 Qt Creator 에 포함된 QML Profiler를 사용하는 것입니다. 애플리케이션에서 시간이 어디에 쓰이는지 알면 잠재적으로 존재하는 문제 영역이 아니라 실제로 존재하는 문제 영역에 집중할 수 있습니다. QML 프로파일링 도구 사용 방법에 대한 자세한 내용은 Qt Creator 설명서를 참조하세요.

가장 자주 실행되는 바인딩 또는 애플리케이션이 가장 많은 시간을 소비하는 기능을 파악하면 문제 영역을 최적화할지 아니면 애플리케이션의 일부 구현 세부 사항을 재설계하여 성능을 개선할지 결정할 수 있습니다. 프로파일링 없이 코드 최적화를 시도하면 성능이 크게 개선되기보다는 매우 미미하게 개선될 가능성이 높습니다.

자바스크립트 코드

대부분의 QML 애플리케이션에는 동적 함수, 신호 처리기, 속성 바인딩 표현식 등의 형태로 많은 양의 JavaScript 코드가 포함되어 있습니다. 이는 일반적으로 문제가 되지 않습니다. 바인딩 컴파일러와 같은 QML 엔진의 일부 최적화 덕분에 일부 사용 사례에서는 C++ 함수를 호출하는 것보다 더 빠를 수 있습니다. 하지만 불필요한 처리가 실수로 트리거되지 않도록 주의를 기울여야 합니다.

타입 변환

JavaScript를 사용할 때 발생하는 주요 비용 중 하나는 대부분의 경우 QML 유형의 프로퍼티에 액세스할 때 기본 C++ 데이터(또는 이에 대한 참조)가 포함된 외부 리소스가 있는 JavaScript 객체가 생성된다는 것입니다. 대부분의 경우 이 작업은 상당히 저렴하지만 경우에 따라서는 상당히 비쌀 수 있습니다. 비용이 많이 드는 경우의 한 가지 예로 C++ QVariantMap Q_PROPERTY 을 QML "변형" 프로퍼티에 할당하는 경우를 들 수 있습니다. 특정 유형의 시퀀스(QList 의 int, qreal, bool, QString, QUrl)는 저렴하지만, 다른 목록 유형은 값비싼 변환 비용(새 JavaScript 배열을 생성하고 새 유형을 하나씩 추가하여 C++ 유형 인스턴스에서 JavaScript 값으로 유형별 변환)이 발생할 수 있습니다.

일부 기본 속성 유형(예: "문자열" 및 "URL" 속성)을 변환하는 데도 비용이 많이 들 수 있습니다. 가장 일치하는 속성 유형을 사용하면 불필요한 변환을 피할 수 있습니다.

QVariantMap 을 QML에 노출해야 하는 경우 "variant" 속성보다는 "var" 속성을 사용하세요. 일반적으로 QtQuick 2.0 이상의 모든 사용 사례("property variant"는 더 이상 사용되지 않는 것으로 표시됨)에서는 "property var"가 진정한 JavaScript 참조를 저장할 수 있으므로(특정 표현식에서 필요한 변환 횟수를 줄일 수 있음) "property var"보다 우수한 것으로 간주해야 합니다.

속성 확인

프로퍼티 확인에는 시간이 걸립니다. 경우에 따라 조회 결과를 캐시하여 재사용할 수 있지만, 가능하면 불필요한 작업을 하지 않는 것이 가장 좋습니다.

다음 예제에서는 자주 실행되는 코드 블록(이 경우 명시적 루프의 내용이지만, 예를 들어 일반적으로 평가되는 바인딩 표현식일 수도 있음)이 있고, 그 안에서 'rect' id와 해당 'color' 속성을 가진 객체를 여러 번 확인합니다:

// bad.qml
import QtQuick

Item {
    width: 400
    height: 200
    Rectangle {
        id: rect
        anchors.fill: parent
        color: "blue"
    }

    function printValue(which, value) {
        console.log(which + " = " + value);
    }

    Component.onCompleted: {
        var t0 = new Date();
        for (var i = 0; i < 1000; ++i) {
            printValue("red", rect.color.r);
            printValue("green", rect.color.g);
            printValue("blue", rect.color.b);
            printValue("alpha", rect.color.a);
        }
        var t1 = new Date();
        console.log("Took: " + (t1.valueOf() - t0.valueOf()) + " milliseconds for 1000 iterations");
    }
}

대신 블록에서 공통 기반을 한 번만 확인할 수 있습니다:

// good.qml
import QtQuick

Item {
    width: 400
    height: 200
    Rectangle {
        id: rect
        anchors.fill: parent
        color: "blue"
    }

    function printValue(which, value) {
        console.log(which + " = " + value);
    }

    Component.onCompleted: {
        var t0 = new Date();
        for (var i = 0; i < 1000; ++i) {
            var rectColor = rect.color; // resolve the common base.
            printValue("red", rectColor.r);
            printValue("green", rectColor.g);
            printValue("blue", rectColor.b);
            printValue("alpha", rectColor.a);
        }
        var t1 = new Date();
        console.log("Took: " + (t1.valueOf() - t0.valueOf()) + " milliseconds for 1000 iterations");
    }
}

이 간단한 변경만으로도 성능이 크게 향상됩니다. 위 코드는 다음과 같이 루프 처리 중에 조회되는 프로퍼티가 변경되지 않으므로 프로퍼티 해상도를 루프 밖으로 끌어올리면 훨씬 더 개선될 수 있습니다(루프 처리 중에는 프로퍼티가 변경되지 않으므로):

// better.qml
import QtQuick

Item {
    width: 400
    height: 200
    Rectangle {
        id: rect
        anchors.fill: parent
        color: "blue"
    }

    function printValue(which, value) {
        console.log(which + " = " + value);
    }

    Component.onCompleted: {
        var t0 = new Date();
        var rectColor = rect.color; // resolve the common base outside the tight loop.
        for (var i = 0; i < 1000; ++i) {
            printValue("red", rectColor.r);
            printValue("green", rectColor.g);
            printValue("blue", rectColor.b);
            printValue("alpha", rectColor.a);
        }
        var t1 = new Date();
        console.log("Took: " + (t1.valueOf() - t0.valueOf()) + " milliseconds for 1000 iterations");
    }
}

프로퍼티 바인딩

프로퍼티 바인딩 표현식은 참조하는 프로퍼티가 변경되면 다시 평가됩니다. 따라서 바인딩 표현식은 가능한 한 단순하게 유지해야 합니다.

일부 처리를 수행하지만 처리의 최종 결과만 중요한 루프가 있는 경우, 누적의 중간 단계에서 바인딩 표현식의 재평가를 트리거하지 않으려면 속성 자체를 점진적으로 업데이트하는 것보다 나중에 업데이트해야 하는 속성에 할당하는 임시 누적기를 업데이트하는 것이 더 나은 경우가 많습니다.

다음 예시는 이 점을 잘 보여줍니다:

// bad.qml
import QtQuick

Item {
    id: root
    width: 200
    height: 200
    property int accumulatedValue: 0

    Text {
        anchors.fill: parent
        text: root.accumulatedValue.toString()
        onTextChanged: console.log("text binding re-evaluated")
    }

    Component.onCompleted: {
        var someData = [ 1, 2, 3, 4, 5, 20 ];
        for (var i = 0; i < someData.length; ++i) {
            accumulatedValue = accumulatedValue + someData[i];
        }
    }
}

onCompleted 핸들러의 루프는 "text" 속성 바인딩을 6번 재평가하게 합니다(그러면 텍스트 값에 의존하는 다른 속성 바인딩과 onTextChanged 신호 핸들러가 매번 재평가되고 매번 표시할 텍스트를 배치하게 됩니다). 이 경우에는 누적의 최종 값만 중요하기 때문에 이 작업은 분명히 불필요합니다.

다음과 같이 다시 작성할 수 있습니다:

// good.qml
import QtQuick

Item {
    id: root
    width: 200
    height: 200
    property int accumulatedValue: 0

    Text {
        anchors.fill: parent
        text: root.accumulatedValue.toString()
        onTextChanged: console.log("text binding re-evaluated")
    }

    Component.onCompleted: {
        var someData = [ 1, 2, 3, 4, 5, 20 ];
        var temp = accumulatedValue;
        for (var i = 0; i < someData.length; ++i) {
            temp = temp + someData[i];
        }
        accumulatedValue = temp;
    }
}

시퀀스 팁

앞서 언급했듯이 일부 시퀀스 유형은 빠른 반면(예: QList<int>, QList<qreal>, QList<bool>, QList<QString>, QStringListQList<QUrl>) 다른 유형은 훨씬 느립니다. 가능한 한 느린 유형 대신 이러한 유형을 사용하는 것 외에도 최상의 성능을 얻기 위해 알아야 할 몇 가지 성능 관련 시맨틱이 있습니다.

첫째, 시퀀스 유형에는 두 가지 구현이 있는데, 하나는 시퀀스가 QObjectQ_PROPERTY (참조 시퀀스라고 부릅니다)인 경우이고, 다른 하나는 시퀀스가 Q_INVOKABLE 함수의 QObject (복사 시퀀스라고 부릅니다)에서 반환되는 경우입니다.

참조 시퀀스는 QMetaObject::property()를 통해 읽고 쓰므로 QVariant 로 읽고 쓰게 됩니다. 즉, JavaScript에서 시퀀스의 요소 값을 변경하면 전체 시퀀스를 QObject 에서 읽고( QVariant 로 읽은 다음 올바른 유형의 시퀀스로 캐스팅), 해당 시퀀스에서 지정된 인덱스의 요소를 변경하고, 전체 시퀀스를 QObject 에 다시 쓰고( QVariant 로) 세 단계가 수행됩니다.

복사 시퀀스는 실제 시퀀스가 JavaScript 객체의 리소스 데이터에 저장되므로 읽기/수정/쓰기 사이클이 발생하지 않으므로 훨씬 더 간단합니다(대신 리소스 데이터가 직접 수정됩니다).

따라서 참조 시퀀스의 요소에 대한 쓰기는 복사 시퀀스의 요소에 대한 쓰기보다 훨씬 느립니다. 실제로 N-요소 참조 시퀀스의 단일 요소에 쓰는 것은 해당 참조 시퀀스에 N-요소 복사 시퀀스를 할당하는 것과 비용이 동일하므로 일반적으로 계산 중에 임시 복사 시퀀스를 수정한 다음 그 결과를 참조 시퀀스에 할당하는 것이 더 낫습니다.

다음 C++ 유형의 존재(그리고 "Qt.example" 네임스페이스에 사전 등록)를 가정합니다:

class SequenceTypeExample : public QQuickItem
{
    Q_OBJECT
    Q_PROPERTY (QList<qreal> qrealListProperty READ qrealListProperty WRITE setQrealListProperty NOTIFY qrealListPropertyChanged)

public:
    SequenceTypeExample() : QQuickItem() { m_list << 1.1 << 2.2 << 3.3; }
    ~SequenceTypeExample() {}

    QList<qreal> qrealListProperty() const { return m_list; }
    void setQrealListProperty(const QList<qreal> &list) { m_list = list; emit qrealListPropertyChanged(); }

signals:
    void qrealListPropertyChanged();

private:
    QList<qreal> m_list;
};

다음 예제는 참조 시퀀스의 요소에 타이트한 루프로 쓰기 때문에 성능이 저하됩니다:

// bad.qml
import QtQuick
import Qt.example

SequenceTypeExample {
    id: root
    width: 200
    height: 200

    Component.onCompleted: {
        var t0 = new Date();
        qrealListProperty.length = 100;
        for (var i = 0; i < 500; ++i) {
            for (var j = 0; j < 100; ++j) {
                qrealListProperty[j] = j;
            }
        }
        var t1 = new Date();
        console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
    }
}

"qrealListProperty[j] = j" 표현식으로 인한 내부 루프에서의 QObject 속성 읽기 및 쓰기는 이 코드를 매우 최악으로 만듭니다. 대신 기능적으로 동등하지만 훨씬 더 빠른 코드를 사용하는 것이 좋습니다:

// good.qml
import QtQuick
import Qt.example

SequenceTypeExample {
    id: root
    width: 200
    height: 200

    Component.onCompleted: {
        var t0 = new Date();
        var someData = [1.1, 2.2, 3.3]
        someData.length = 100;
        for (var i = 0; i < 500; ++i) {
            for (var j = 0; j < 100; ++j) {
                someData[j] = j;
            }
            qrealListProperty = someData;
        }
        var t1 = new Date();
        console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
    }
}

둘째, 프로퍼티의 요소가 변경되면 프로퍼티에 대한 변경 신호가 발생합니다. 시퀀스 속성에서 특정 요소에 대한 바인딩이 많은 경우 해당 요소에 바인딩되는 동적 속성을 만들고 해당 동적 속성을 시퀀스 요소 대신 바인딩 표현식의 기호로 사용하면 값이 변경될 때만 바인딩이 다시 평가되므로 더 좋습니다.

이는 대부분의 클라이언트가 절대 사용하지 않는 특이한 사용 사례이지만, 이와 같은 작업을 수행하는 경우를 대비하여 알아두면 좋습니다:

// bad.qml
import QtQuick
import Qt.example

SequenceTypeExample {
    id: root

    property int firstBinding: qrealListProperty[1] + 10;
    property int secondBinding: qrealListProperty[1] + 20;
    property int thirdBinding: qrealListProperty[1] + 30;

    Component.onCompleted: {
        var t0 = new Date();
        for (var i = 0; i < 1000; ++i) {
            qrealListProperty[2] = i;
        }
        var t1 = new Date();
        console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
    }
}

루프에서 인덱스 2의 요소만 수정되더라도 변경 신호의 세분성은 전체 프로퍼티가 변경되었다는 것이므로 세 개의 바인딩이 모두 다시 평가됩니다. 따라서 중간 바인딩을 추가하는 것이 때때로 유용할 수 있습니다:

// good.qml
import QtQuick
import Qt.example

SequenceTypeExample {
    id: root

    property int intermediateBinding: qrealListProperty[1]
    property int firstBinding: intermediateBinding + 10;
    property int secondBinding: intermediateBinding + 20;
    property int thirdBinding: intermediateBinding + 30;

    Component.onCompleted: {
        var t0 = new Date();
        for (var i = 0; i < 1000; ++i) {
            qrealListProperty[2] = i;
        }
        var t1 = new Date();
        console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
    }
}

위의 예에서는 중간 바인딩만 매번 재평가되므로 성능이 크게 향상됩니다.

값 유형 팁

값 유형 속성(글꼴, 색상, 벡터3D 등)은 QObject 속성과 유사하며 알림 시맨틱을 시퀀스 유형 속성으로 변경합니다. 따라서 시퀀스에 대해 위에서 설명한 팁은 값 유형 프로퍼티에도 적용할 수 있습니다. 값 유형의 경우 일반적으로 값 유형의 하위 프로퍼티 수가 시퀀스의 요소 수보다 훨씬 적기 때문에 문제가 덜하지만, 불필요하게 재평가되는 바인딩 수가 증가하면 성능에 부정적인 영향을 미칠 수 있습니다.

일반적인 성능 팁

언어 설계에서 비롯된 일반적인 JavaScript 성능 고려 사항은 QML에도 적용됩니다. 가장 눈에 띄는 것은

  • 가능하면 eval() 사용을 피하세요.
  • 객체의 프로퍼티를 삭제하지 마세요.

공통 인터페이스 요소

텍스트 엘리먼트

텍스트 레이아웃을 계산하는 작업은 느릴 수 있습니다. 레이아웃 엔진에 필요한 작업량을 줄일 수 있으므로 가능하면 StyledText 대신 PlainText 형식을 사용하는 것이 좋습니다. PlainText 을 사용할 수 없는 경우(이미지를 포함하거나 태그를 사용하여 전체 텍스트가 아닌 특정 서식(굵게, 이탤릭체 등)을 갖도록 문자 범위를 지정해야 하는 경우)에는 StyledText 을 사용해야 합니다.

이 모드에서는 구문 분석 비용이 발생하므로 텍스트가 StyledText 이 아닌 경우에만 AutoText 을 사용해야 합니다. RichText 모드는 StyledText 에서 거의 모든 기능을 저렴한 비용으로 제공하므로 사용해서는 안 됩니다.

이미지

이미지는 모든 사용자 인터페이스에서 중요한 부분입니다. 안타깝게도 이미지를 로드하는 데 걸리는 시간, 메모리 사용량, 사용 방식 때문에 문제의 큰 원인이 되기도 합니다.

비동기 로딩

이미지는 크기가 상당히 큰 경우가 많으므로 이미지 로딩이 UI 스레드를 차단하지 않도록 하는 것이 좋습니다. 사용자 인터페이스의 미관에 부정적인 영향을 미치지 않는 로컬 파일 시스템(원격 이미지는 항상 비동기적으로 로드됨)에서 이미지를 비동기적으로 로드하려면 QML 이미지 요소의 "비동기" 속성을 true 로 설정하세요.

'비동기' 속성이 true 로 설정된 이미지 요소는 우선순위가 낮은 작업자 스레드에서 이미지를 로드합니다.

명시적 소스 크기

애플리케이션에서 큰 이미지를 로드하지만 작은 크기의 요소에 표시하는 경우 "sourceSize" 속성을 렌더링되는 요소의 크기로 설정하여 큰 이미지가 아닌 작은 크기의 이미지가 메모리에 유지되도록 합니다.

sourceSize를 변경하면 이미지가 다시 로드될 수 있으니 주의하세요.

런타임 컴포지션 피하기

또한 애플리케이션에 미리 컴포지션된 이미지 리소스를 제공하면 런타임에 컴포지션 작업을 하지 않아도 됩니다(예: 그림자 효과가 있는 요소 제공).

이미지 스무딩 방지

필요한 경우에만 image.smooth 을 활성화합니다. 일부 하드웨어에서는 속도가 느려지고 이미지가 자연스러운 크기로 표시되는 경우 시각적 효과가 없습니다.

페인팅

같은 영역을 여러 번 칠하지 마세요. 배경을 여러 번 칠하지 않으려면 사각형 대신 항목을 루트 요소로 사용하세요.

앵커로 요소 배치하기

바인딩보다는 앵커를 사용하여 서로를 기준으로 항목을 배치하는 것이 더 효율적입니다. 바인딩을 사용하여 rect2를 rect1에 상대적으로 배치하는 경우를 예로 들어 보겠습니다:

Rectangle {
    id: rect1
    x: 20
    width: 200; height: 200
}
Rectangle {
    id: rect2
    x: rect1.x
    y: rect1.y + rect1.height
    width: rect1.width - 20
    height: 200
}

앵커를 사용하면 이 작업을 더 효율적으로 수행할 수 있습니다:

Rectangle {
    id: rect1
    x: 20
    width: 200; height: 200
}
Rectangle {
    id: rect2
    height: 200
    anchors.left: rect1.left
    anchors.top: rect1.bottom
    anchors.right: rect1.right
    anchors.rightMargin: 20
}

앵커를 사용하는 대신 시각적 객체의 x, y, 너비 및 높이 속성에 바인딩 표현식을 할당하여 바인딩을 사용한 위치 지정은 상대적으로 느리지만 유연성을 극대화할 수 있습니다.

레이아웃이 동적이지 않은 경우 레이아웃을 지정하는 가장 효율적인 방법은 x, y, 너비 및 높이 속성을 정적으로 초기화하는 것입니다. 항목 좌표는 항상 부모 좌표에 상대적이므로 부모의 0,0 좌표에서 고정된 오프셋을 원한다면 앵커를 사용해서는 안 됩니다. 다음 예제에서 자식 사각형 객체는 같은 위치에 있지만 표시된 앵커 코드는 정적 초기화를 통해 고정 위치를 사용하는 코드만큼 리소스 효율적이지 않습니다:

Rectangle {
    width: 60
    height: 60
    Rectangle {
        id: fixedPositioning
        x: 20
        y: 20
        width: 20
        height: 20
    }
    Rectangle {
        id: anchorPositioning
        anchors.fill: parent
        anchors.margins: 20
    }
}

모델 및 뷰

대부분의 애플리케이션에는 뷰에 데이터를 공급하는 모델이 하나 이상 있습니다. 애플리케이션 개발자가 성능을 극대화하기 위해 알아야 할 몇 가지 의미론이 있습니다.

사용자 지정 C++ 모델

QML에서 뷰와 함께 사용하기 위해 C++로 사용자 지정 모델을 작성하는 것이 바람직한 경우가 많습니다. 이러한 모델의 최적 구현은 충족해야 하는 사용 사례에 따라 크게 달라지지만, 몇 가지 일반적인 지침은 다음과 같습니다:

  • 가능한 한 비동기화
  • 우선순위가 낮은 워커 스레드에서 모든 처리를 수행합니다.
  • 백엔드 작업을 일괄 처리하여 (잠재적으로 느릴 수 있는) I/O 및 IPC가 최소화되도록 합니다.

우선순위가 낮은 작업자 스레드를 사용하면 GUI 스레드가 고갈될 위험(체감 성능이 저하될 수 있음)을 최소화할 수 있다는 점에 유의하세요. 또한 동기화 및 잠금 메커니즘은 성능 저하의 중요한 원인이 될 수 있으므로 불필요한 잠금을 피하기 위해 주의를 기울여야 한다는 점을 기억하세요.

리스트모델 QML 유형

QML은 ListView 에 데이터를 공급하는 데 사용할 수 있는 ListModel 유형을 제공합니다. 이 유형은 대부분의 사용 사례에 충분하며 올바르게 사용하기만 하면 비교적 성능이 좋습니다.

워커 스레드 내에서 채우기

ListModel 엘리먼트는 자바스크립트의 (우선순위가 낮은) 워커 스레드에서 채울 수 있습니다. 개발자는 WorkerScript 내에서 ListModel 에서 명시적으로 "sync()"를 호출하여 변경 사항을 메인 스레드에 동기화해야 합니다. 자세한 내용은 WorkerScript 문서를 참조하세요.

WorkerScript 요소를 사용하면 자바스크립트 엔진이 스레드별로 작동하므로 별도의 자바스크립트 엔진이 생성된다는 점에 유의하세요. 이로 인해 메모리 사용량이 증가합니다. 그러나 여러 개의 WorkerScript 요소는 모두 동일한 작업자 스레드를 사용하므로 애플리케이션에서 이미 하나의 요소를 사용하고 있다면 두 번째 또는 세 번째 WorkerScript 요소 사용으로 인한 메모리 영향은 무시할 수 있습니다.

동적 역할 사용 금지

QtQuick 2의 ListModel 요소는 QtQuick 1보다 훨씬 더 성능이 좋습니다. 성능 향상은 주로 주어진 모델에서 각 요소 내의 역할 유형에 대한 가정에서 비롯되며, 유형이 변경되지 않는 경우 캐싱 성능이 크게 향상됩니다. 유형이 요소마다 동적으로 변경될 수 있다면 이러한 최적화가 불가능해지며 모델의 성능은 훨씬 더 나빠질 것입니다.

따라서 동적 입력은 기본적으로 비활성화되어 있으며, 개발자가 모델의 부울 "dynamicRoles" 속성을 구체적으로 설정해야 동적 입력이 활성화됩니다(그에 따른 성능 저하를 감수해야 함). 동적 타이핑을 사용하지 않도록 애플리케이션을 재설계할 수 있는 경우에는 동적 타이핑을 사용하지 않는 것이 좋습니다.

뷰 델리게이트는 가능한 한 단순하게 유지해야 합니다. 델리게이트에 필요한 정보를 표시할 수 있을 만큼의 QML만 넣으세요. 즉시 필요하지 않은 추가 기능(예: 클릭 시 추가 정보를 표시하는 경우)은 필요할 때까지 만들지 않아야 합니다(향후 지연 초기화에 대한 섹션 참조).

다음 목록은 델리게이트를 디자인할 때 염두에 두어야 할 사항을 잘 요약한 것입니다:

  • 델리게이트에 포함된 요소 수가 적을수록 더 빨리 생성할 수 있으므로 뷰를 더 빠르게 스크롤할 수 있습니다.
  • 델리게이트의 바인딩 수를 최소한으로 유지하고, 특히 델리게이트 내에서 상대적 위치를 지정할 때는 바인딩 대신 앵커를 사용하세요.
  • 델리게이트 내에서 ShaderEffect 요소를 사용하지 마세요.
  • 델리게이트에서 클리핑을 활성화하지 마세요.

보기의 cacheBuffer 속성을 설정하여 표시 영역 외부에서 델리게이트의 비동기 생성 및 버퍼링을 허용할 수 있습니다. 사소하지 않고 단일 프레임 내에 생성될 가능성이 낮은 뷰 델리게이트에는 cacheBuffer 을 사용하는 것이 좋습니다.

cacheBuffer 은 추가 델리게이트를 인메모리에 유지한다는 점을 명심하세요. 따라서 cacheBuffer 활용을 통해 얻은 값과 추가 메모리 사용량의 균형을 맞춰야 합니다. cacheBuffer 활용으로 인한 메모리 사용량 증가로 인해 드물지만 스크롤 시 프레임 속도가 저하될 수 있으므로 개발자는 벤치마킹을 통해 자신의 사용 사례에 가장 적합한 값을 찾아야 합니다.

시각 효과

Qt Quick 2에는 개발자와 디자이너가 매우 매력적인 사용자 인터페이스를 만들 수 있는 여러 기능이 포함되어 있습니다. 유동성과 동적 전환, 시각 효과는 애플리케이션에서 큰 효과를 발휘할 수 있지만 QML의 일부 기능은 성능에 영향을 미칠 수 있으므로 사용할 때 주의해야 합니다.

애니메이션

일반적으로 프로퍼티에 애니메이션을 적용하면 해당 프로퍼티를 참조하는 모든 바인딩이 다시 평가됩니다. 일반적으로는 이렇게 하는 것이 바람직하지만 다른 경우에는 애니메이션을 수행하기 전에 바인딩을 비활성화한 다음 애니메이션이 완료되면 바인딩을 다시 할당하는 것이 더 나을 수 있습니다.

애니메이션 중에는 JavaScript를 실행하지 마세요. 예를 들어, x 속성 애니메이션의 각 프레임에 대해 복잡한 자바스크립트 표현식을 실행하는 것은 피해야 합니다.

스크립트 애니메이션은 메인 스레드에서 실행되므로 완료하는 데 너무 오래 걸리면 프레임이 건너뛸 수 있으므로 개발자는 특히 주의해서 사용해야 합니다.

파티클

파티클 Qt Quick Particles 모듈을 사용하면 아름다운 파티클 효과를 사용자 인터페이스에 매끄럽게 통합할 수 있습니다. 그러나 플랫폼마다 그래픽 하드웨어 기능이 다르므로 파티클 모듈은 하드웨어가 지원하는 범위로 매개변수를 제한할 수 없습니다. 렌더링하려는 파티클이 많을수록(그리고 파티클이 클수록) 60 FPS로 렌더링하려면 그래픽 하드웨어가 더 빨라져야 합니다. 더 많은 파티클에 영향을 주려면 더 빠른 CPU가 필요합니다. 따라서 대상 플랫폼에서 모든 파티클 효과를 신중하게 테스트하여 60 FPS로 렌더링할 수 있는 파티클의 수와 크기를 보정하는 것이 중요합니다.

파티클 시스템을 사용하지 않을 때(예: 보이지 않는 요소에서) 비활성화하면 불필요한 시뮬레이션을 피할 수 있습니다.

자세한 내용은 파티클 시스템 퍼포먼스 가이드를 참조하세요.

엘리먼트 수명 제어하기

애플리케이션을 단일 QML 파일에 포함된 간단한 모듈식 컴포넌트로 분할하면 애플리케이션 시작 시간을 단축하고 메모리 사용량을 더 효과적으로 제어할 수 있으며, 애플리케이션에서 활성 상태이지만 보이지 않는 요소의 수를 줄일 수 있습니다.

지연 초기화

QML 엔진은 컴포넌트의 로딩 및 초기화로 인해 프레임이 건너뛰지 않도록 하기 위해 몇 가지 까다로운 작업을 수행합니다. 그러나 필요하지 않은 작업을 하지 않고 필요한 작업이 될 때까지 지연시키는 것보다 시작 시간을 줄이는 더 좋은 방법은 없습니다. 이는 Loader 또는 컴포넌트를 동적으로 생성하여 달성할 수 있습니다.

로더 사용

로더는 컴포넌트를 동적으로 로딩 및 언로딩할 수 있는 요소입니다.

  • 로더의 "active" 속성을 사용하면 필요할 때까지 초기화를 지연시킬 수 있습니다.
  • 오버로드된 버전의 "setSource()" 함수를 사용하여 초기 속성 값을 제공할 수 있습니다.
  • Loader asynchronous 속성을 true로 설정하면 컴포넌트가 인스턴스화되는 동안 유동성이 향상될 수도 있습니다.

동적 생성 사용

개발자는 Qt.createComponent() 함수를 사용하여 자바스크립트 내에서 런타임에 컴포넌트를 동적으로 생성한 다음 createObject()를 호출하여 인스턴스화할 수 있습니다. 호출에 지정된 소유권 의미에 따라 개발자는 생성된 객체를 수동으로 삭제해야 할 수도 있습니다. 자세한 내용은 자바스크립트에서 동적 QML 객체 생성을 참조하세요.

사용하지 않는 요소 삭제

보이지 않는 요소의 자식이기 때문에 보이지 않는 요소(예: 탭 위젯에서 첫 번째 탭이 표시되는 동안 두 번째 탭)는 대부분의 경우 느리게 초기화해야 하며 더 이상 사용하지 않을 때는 삭제하여 활성 상태로 두는 데 따른 지속적인 비용(예: 렌더링, 애니메이션, 속성 바인딩 평가 등)을 피할 수 있습니다.

로더 요소로 로드된 항목은 로더의 "source" 또는 "sourceComponent" 속성을 재설정하여 해제할 수 있으며, 다른 항목은 해당 항목에 대해 destroy()를 호출하여 명시적으로 해제할 수 있습니다. 경우에 따라서는 항목을 활성 상태로 유지해야 할 수도 있는데, 이 경우 최소한 보이지 않게 만들어야 합니다.

활성화되어 있지만 보이지 않는 요소에 대한 자세한 내용은 렌더링에 대한 다음 섹션을 참조하세요.

렌더링

QtQuick 2에서 렌더링에 사용되는 씬 그래프를 사용하면 매우 동적이고 애니메이션이 적용된 사용자 인터페이스를 60 FPS로 원활하게 렌더링할 수 있습니다. 그러나 렌더링 성능을 크게 저하시킬 수 있는 몇 가지 요소가 있으므로 개발자는 가능한 한 이러한 함정을 피하도록 주의해야 합니다.

클리핑

클리핑은 기본적으로 비활성화되어 있으며 필요한 경우에만 활성화해야 합니다.

클리핑은 시각적 효과이지 최적화가 아닙니다. 렌더러의 복잡성을 줄이는 것이 아니라 오히려 증가시킵니다. 클리핑이 활성화되면 항목은 자신의 그림뿐만 아니라 하위 항목의 그림도 경계 사각형에 클리핑됩니다. 이렇게 하면 렌더러가 요소의 그리기 순서를 자유롭게 재지정할 수 없으므로 최적이 아닌 최상의 씬 그래프 순회가 발생합니다.

델리게이트 내부 클리핑은 특히 좋지 않으므로 어떤 대가를 치르더라도 피해야 합니다.

오버드로잉 및 보이지 않는 요소

다른 (불투명한) 요소로 완전히 가려진 요소가 있는 경우 해당 요소의 "표시" 속성을 false 로 설정하는 것이 가장 좋습니다. 그렇지 않으면 불필요하게 그려지게 됩니다.

마찬가지로 보이지 않지만(예: 탭 위젯에서 첫 번째 탭은 표시되지만 두 번째 탭은 보이지 않는 경우) 시작 시 초기화해야 하는 요소(예: 탭이 활성화된 경우에만 두 번째 탭을 인스턴스화하는 데 비용이 너무 오래 걸리는 경우)는 그리는 비용을 피하기 위해 "visible" 속성을 false 으로 설정해야 합니다(앞서 설명한 대로 여전히 활성화되어 있으므로 애니메이션 또는 바인딩 평가 비용이 발생하지만).

반투명 대 불투명

일반적으로 불투명 콘텐츠는 반투명 콘텐츠보다 그리는 속도가 훨씬 빠릅니다. 그 이유는 반투명 콘텐츠는 블렌딩이 필요하고 렌더러가 불투명 콘텐츠를 더 잘 최적화할 수 있기 때문입니다.

반투명 픽셀이 하나 있는 이미지는 대부분 불투명하더라도 완전히 반투명한 것으로 처리됩니다. 가장자리가 투명한 BorderImage 의 경우에도 마찬가지입니다.

셰이더

ShaderEffect 유형을 사용하면 오버헤드가 거의 없는 Qt Quick 애플리케이션에 GLSL 코드를 인라인으로 배치할 수 있습니다. 그러나 렌더링된 모양의 모든 픽셀에 대해 조각 프로그램이 실행되어야 한다는 점을 인식하는 것이 중요합니다. 저사양 하드웨어에 배포하고 셰이더가 많은 양의 픽셀을 커버하는 경우 성능 저하를 방지하기 위해 조각 셰이더를 몇 가지 명령으로 유지해야 합니다.

GLSL로 작성된 셰이더를 사용하면 복잡한 변환과 시각 효과를 작성할 수 있지만 주의해서 사용해야 합니다. ShaderEffectSource 을 사용하면 씬이 그려지기 전에 FBO로 미리 렌더링됩니다. 이 추가 오버헤드는 상당히 비쌀 수 있습니다.

메모리 할당 및 수집

애플리케이션이 할당할 메모리 양과 메모리 할당 방식은 매우 중요한 고려 사항입니다. 메모리가 제한된 디바이스에서 메모리 부족 상태에 대한 명백한 우려 외에도 힙에 메모리를 할당하는 것은 계산 비용이 상당히 많이 드는 작업이며 특정 할당 전략은 페이지 전체에서 데이터 조각화를 증가시킬 수 있습니다. 자바스크립트는 자동으로 가비지 수집되는 관리형 메모리 힙을 사용하며, 여기에는 몇 가지 장점이 있지만 몇 가지 중요한 의미도 있습니다.

QML로 작성된 애플리케이션은 C++ 힙과 자동 관리되는 JavaScript 힙의 메모리를 모두 사용합니다. 애플리케이션 개발자는 성능을 극대화하기 위해 각각의 미묘한 차이를 알고 있어야 합니다.

QML 애플리케이션 개발자를 위한 팁

이 섹션에 포함된 팁과 제안은 가이드라인일 뿐이며 모든 상황에 적용되지 않을 수 있습니다. 가능한 한 최선의 결정을 내리기 위해 경험적 지표를 사용하여 애플리케이션을 신중하게 벤치마킹하고 분석해야 합니다.

구성 요소를 느리게 인스턴스화 및 초기화하기

애플리케이션이 여러 개의 보기(예: 여러 개의 탭)로 구성되어 있지만 한 번에 하나만 필요한 경우 지연 인스턴스화를 사용하여 주어진 시간에 할당해야 하는 메모리의 양을 최소화할 수 있습니다. 자세한 내용은 지연 초기화에 대한 이전 섹션을 참조하세요.

사용하지 않는 개체 삭제

컴포넌트를 지연 로드하거나 JavaScript 표현식 중에 동적으로 객체를 생성하는 경우 자동 가비지 수집을 기다리는 것보다 수동으로 destroy() 삭제하는 것이 더 나은 경우가 많습니다. 자세한 내용은 엘리먼트 수명 제어에 대한 이전 섹션을 참조하세요.

가비지 수집기를 수동으로 호출하지 않기

대부분의 경우 가비지 수집기를 수동으로 호출하면 상당한 시간 동안 GUI 스레드가 차단되므로 가비지 수집기를 수동으로 호출하는 것은 현명하지 않습니다. 이로 인해 프레임이 건너뛰거나 애니메이션이 끊길 수 있으므로 어떤 대가를 치르더라도 피해야 합니다.

가비지 수집기를 수동으로 호출하는 것이 허용되는 경우도 있지만(이에 대해서는 다음 섹션에서 자세히 설명합니다), 대부분의 경우 가비지 수집기를 호출하는 것은 불필요하고 비생산적입니다.

동일한 암시적 유형을 여러 개 정의하지 마세요.

QML 요소에 QML에 정의된 사용자 정의 속성이 있는 경우 해당 속성은 자체 암시적 유형이 됩니다. 이에 대해서는 다음 섹션에서 자세히 설명합니다. 컴포넌트에 여러 개의 동일한 암시적 유형이 인라인으로 정의되어 있으면 일부 메모리가 낭비됩니다. 이러한 상황에서는 일반적으로 재사용할 수 있는 새 컴포넌트를 명시적으로 정의하는 것이 좋습니다.

사용자 정의 프로퍼티를 정의하면 성능 최적화에 도움이 되거나(예: 필요하거나 재평가되는 바인딩 수를 줄이기 위해) 컴포넌트의 모듈성과 유지보수성을 향상시킬 수 있습니다. 이러한 경우 사용자 정의 프로퍼티를 사용하는 것이 좋습니다. 그러나 새 유형이 두 번 이상 사용되는 경우 메모리를 절약하기 위해 자체 컴포넌트(.qml 파일)로 분할해야 합니다.

기존 컴포넌트 재사용

새 컴포넌트를 정의하려는 경우 해당 컴포넌트가 플랫폼의 컴포넌트 세트에 이미 존재하지 않는지 다시 한 번 확인하는 것이 좋습니다. 그렇지 않으면 QML 엔진이 기존에 이미 로드된 다른 컴포넌트의 복제본인 유형에 대한 유형 데이터를 생성하고 저장하도록 강제할 수 있습니다.

프래그마 라이브러리 스크립트 대신 싱글톤 유형 사용

프라그마 라이브러리 스크립트를 사용하여 애플리케이션 전체 인스턴스 데이터를 저장하는 경우 QObject 싱글톤 유형을 대신 사용하는 것을 고려하세요. 이렇게 하면 성능이 향상되고 자바스크립트 힙 메모리를 덜 사용하게 됩니다.

QML 애플리케이션의 메모리 할당

QML 애플리케이션의 메모리 사용량은 C++ 힙 사용량과 자바스크립트 힙 사용량의 두 부분으로 나눌 수 있습니다. 각각에 할당된 메모리 중 일부는 QML 엔진 또는 JavaScript 엔진에 의해 할당되므로 피할 수 없지만 나머지는 애플리케이션 개발자가 내린 결정에 따라 달라집니다.

C++ 힙에는 다음이 포함됩니다:

  • QML 엔진의 고정적이고 피할 수 없는 오버헤드(구현 데이터 구조, 컨텍스트 정보 등);
  • 컴포넌트별 컴파일된 데이터 및 유형 정보(유형별 속성 메타데이터 포함)(애플리케이션이 로드하는 모듈 및 컴포넌트에 따라 QML 엔진에서 생성됨);
  • 애플리케이션이 인스턴스화하는 컴포넌트에 따라 객체별 C++ 데이터(속성 값 포함)와 요소별 메타객체 계층 구조;
  • QML 가져오기(라이브러리)에 의해 특별히 할당된 모든 데이터.

자바스크립트 힙에는 다음이 포함됩니다:

  • JavaScript 엔진 자체의 고정적이고 피할 수 없는 오버헤드(내장된 JavaScript 유형 포함);
  • JavaScript 통합의 고정적이고 피할 수 없는 오버헤드(로드된 유형, 함수 템플릿 등에 대한 생성자 함수 등)
  • 각 유형에 대해 런타임에 JavaScript 엔진이 생성한 유형별 레이아웃 정보 및 기타 내부 유형 데이터(유형에 관한 아래 참고 사항 참조);
  • 객체별 JavaScript 데이터("var" 속성, JavaScript 함수 및 시그널 핸들러, 최적화되지 않은 바인딩 표현식);
  • 표현식 평가 중에 할당된 변수.

또한 메인 스레드에서 사용하기 위해 할당된 JavaScript 힙이 하나 있고, 선택적으로 WorkerScript 스레드에서 사용하기 위해 할당된 다른 JavaScript 힙이 하나 더 있습니다. 애플리케이션에서 WorkerScript 요소를 사용하지 않는 경우 해당 오버헤드는 발생하지 않습니다. JavaScript 힙의 크기는 수 메가바이트가 될 수 있으므로 메모리가 제한된 디바이스용으로 작성된 애플리케이션은 목록 모델을 비동기적으로 채우는 데 유용하지만 WorkerScript 요소를 피하는 것이 가장 좋을 수 있습니다.

QML 엔진과 JavaScript 엔진 모두 관찰된 유형에 대한 자체 유형 데이터 캐시를 자동으로 생성한다는 점에 유의하세요. 애플리케이션에서 로드되는 모든 컴포넌트는 고유한(명시적) 유형이며, QML에서 자체 사용자 정의 속성을 정의하는 모든 요소(컴포넌트 인스턴스)는 암시적 유형입니다. 사용자 정의 속성을 정의하지 않는 모든 요소(컴포넌트의 인스턴스)는 JavaScript 및 QML 엔진에서 자체 암시적 유형이 아닌 컴포넌트가 명시적으로 정의한 유형으로 간주됩니다.

다음 예시를 살펴보겠습니다:

import QtQuick

Item {
    id: root

    Rectangle {
        id: r0
        color: "red"
    }

    Rectangle {
        id: r1
        color: "blue"
        width: 50
    }

    Rectangle {
        id: r2
        property int customProperty: 5
    }

    Rectangle {
        id: r3
        property string customProperty: "hello"
    }

    Rectangle {
        id: r4
        property string customProperty: "hello"
    }
}

앞의 예에서 사각형 r0r1 에는 사용자 정의 속성이 없으므로 JavaScript 및 QML 엔진은 둘 다 같은 유형으로 간주합니다. 즉, r0r1 은 모두 명시적으로 정의된 Rectangle 유형으로 간주됩니다. 직사각형 r2, r3r4 은 각각 사용자 정의 속성을 가지며 각각 다른 (암시적) 유형으로 간주됩니다. r3r4 은 동일한 속성 정보를 가지고 있더라도 인스턴스인 컴포넌트에서 사용자 정의 속성이 선언되지 않았기 때문에 각각 다른 유형으로 간주됩니다.

r3r4 이 모두 RectangleWithString 컴포넌트의 인스턴스이고 해당 컴포넌트 정의에 customProperty 라는 문자열 속성 선언이 포함된 경우 r3r4 는 동일한 유형으로 간주됩니다(즉, 자체 암시적 유형을 정의하는 것이 아니라 RectangleWithString 유형의 인스턴스가 됩니다).

심층적인 메모리 할당 고려 사항

메모리 할당 또는 성능 트레이드오프에 관한 결정을 내릴 때마다 CPU 캐시 성능, 운영 체제 페이징 및 JavaScript 엔진 가비지 수집의 영향을 염두에 두는 것이 중요합니다. 최상의 솔루션을 선택할 수 있도록 잠재적인 솔루션을 신중하게 벤치마킹해야 합니다.

어떤 일반적인 가이드라인도 컴퓨터 과학의 기본 원리에 대한 확실한 이해와 애플리케이션 개발자가 개발 중인 플랫폼의 구현 세부 사항에 대한 실질적인 지식을 대체할 수 없습니다. 또한, 트레이드오프 결정을 내릴 때 아무리 이론적인 계산을 해도 좋은 벤치마크와 분석 도구 세트를 대체할 수는 없습니다.

조각화

조각화는 C++ 개발 문제입니다. 애플리케이션 개발자가 C++ 유형이나 플러그인을 정의하지 않는다면 이 섹션을 무시해도 됩니다.

시간이 지남에 따라 애플리케이션은 메모리의 많은 부분을 할당하고, 해당 메모리에 데이터를 쓰고, 일부 데이터 사용을 마친 후에는 메모리의 일부를 해제합니다. 이로 인해 "사용 가능한" 메모리가 비연속적인 청크에 위치하게 되어 다른 애플리케이션이 사용할 수 있도록 운영 체제로 반환할 수 없게 될 수 있습니다. 또한 '살아있는' 데이터가 물리적 메모리의 여러 페이지에 분산되어 있을 수 있으므로 애플리케이션의 캐싱 및 액세스 특성에도 영향을 미칩니다. 이로 인해 운영 체제가 강제로 스왑되어 파일 시스템 I/O가 발생할 수 있으며, 이는 상대적으로 매우 느린 작동을 유발할 수 있습니다.

조각화는 풀 할당기(및 기타 인접 메모리 할당기)를 활용하거나, 객체 수명을 신중하게 관리하여 한 번에 할당되는 메모리 양을 줄이거나, 주기적으로 캐시를 정리 및 재구축하거나, 가비지 컬렉션을 통해 메모리 관리 런타임(JavaScript 등)을 활용함으로써 피할 수 있습니다.

가비지 컬렉션

자바스크립트는 가비지 컬렉션을 제공합니다. 자바스크립트 힙에 할당된 메모리(C++ 힙이 아닌)는 자바스크립트 엔진이 소유합니다. 엔진은 자바스크립트 힙에서 참조되지 않은 모든 데이터를 주기적으로 수집합니다.

가비지 컬렉션의 의미

가비지 컬렉션에는 장단점이 있습니다. 객체 수명을 수동으로 관리하는 것이 덜 중요하다는 뜻입니다. 하지만 애플리케이션 개발자가 통제할 수 없는 시점에 자바스크립트 엔진이 잠재적으로 오래 지속될 수 있는 작업을 시작할 수 있다는 의미이기도 합니다. 애플리케이션 개발자가 자바스크립트 힙 사용을 신중하게 고려하지 않으면 가비지 수집의 빈도와 기간이 애플리케이션 환경에 부정적인 영향을 미칠 수 있습니다.

가비지 수집기를 수동으로 호출하기

QML로 작성된 애플리케이션은 어느 단계에서 가비지 수집을 수행해야 할 가능성이 높습니다. 가비지 수집은 사용 가능한 여유 메모리가 부족할 때 JavaScript 엔진에 의해 자동으로 트리거되지만, 애플리케이션 개발자가 가비지 수집기를 언제 수동으로 호출할지 결정하는 것이 더 나은 경우도 있습니다(보통은 그렇지 않지만).

애플리케이션 개발자는 애플리케이션이 상당한 시간 동안 유휴 상태가 되는 시기를 가장 잘 파악하고 있을 가능성이 높습니다. QML 애플리케이션이 자바스크립트 힙 메모리를 많이 사용하여 특히 성능에 민감한 작업(예: 목록 스크롤, 애니메이션 등) 중에 가비지 수집 주기가 정기적으로 중단되는 경우, 애플리케이션 개발자는 활동이 없는 기간 동안 가비지 수집기를 수동으로 호출하는 것이 좋을 수 있습니다. 유휴 기간은 가비지 수집을 수행하기에 이상적인데, 활동 중에 가비지 수집기를 호출할 경우 발생할 수 있는 사용자 경험 저하(프레임 건너뛰기, 애니메이션 끊김 등)를 사용자가 알아차리지 못하기 때문입니다.

가비지 수집기는 자바스크립트 내에서 gc() 을 호출하여 수동으로 호출할 수 있습니다. 이렇게 하면 포괄적인 수집 주기가 수행되어 완료하는 데 수백 밀리초에서 천 밀리초 이상 걸릴 수 있으므로 가능하면 피해야 합니다.

메모리와 성능의 트레이드 오프

일부 상황에서는 처리 시간 단축을 위해 메모리 사용량 증가를 절충할 수 있습니다. 예를 들어, 타이트 루프에서 사용되는 심볼 조회 결과를 JavaScript 표현식의 임시 변수에 캐싱하면 해당 표현식을 평가할 때 성능이 크게 향상되지만 임시 변수를 할당해야 합니다. 위의 경우와 같이 이러한 절충안이 합리적인 경우도 있지만(거의 항상 합리적인 경우), 다른 경우에는 시스템의 메모리 압박을 피하기 위해 처리 시간이 약간 더 오래 걸리도록 허용하는 것이 더 나을 수도 있습니다.

어떤 경우에는 메모리 사용량 증가로 인한 영향이 극심할 수 있습니다. 일부 상황에서는 성능 향상을 위해 메모리 사용량을 줄이면 페이지 쓰레시 또는 캐시 쓰레시가 증가하여 성능이 크게 저하될 수 있습니다. 주어진 상황에서 어떤 솔루션이 가장 적합한지 결정하기 위해서는 항상 트레이드 오프의 영향을 신중하게 벤치마킹해야 합니다.

캐시 성능과 메모리 시간 트레이드오프에 대한 자세한 내용은 다음 문서를 참조하세요:

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.