성능 고려 사항 및 제안 사항
타이밍 고려 사항
애플리케이션 개발자는 일반적으로 렌더링 엔진이 초당 60프레임의 일관된 새로고침 빈도를 유지하도록 노력합니다. 하드웨어와 요구 사항에 따라 수치는 달라질 수 있지만 60FPS는 매우 일반적입니다. 60 FPS는 그래픽 하드웨어에 드로잉 프리미티브를 업로드하는 데 필요한 처리 시간을 포함하여 각 프레임 사이에 약 16밀리초의 처리 시간이 있다는 것을 의미합니다.
실제로 이는 애플리케이션 개발자가
- 가능한 경우 비동기 이벤트 중심 프로그래밍을 사용합니다.
- 작업자 스레드를 사용하여 중요한 처리를 수행합니다.
- 이벤트 루프를 수동으로 돌리지 않습니다.
- 블로킹 함수 내에서 프레임당 몇 밀리초 이상을 사용하지 않습니다.
그렇게 하지 않으면 프레임을 건너뛰게 되어 사용자 경험에 큰 영향을 미칩니다.
참고: 유혹적이지만 절대 사용해서는 안 되는 패턴은 QML에서 호출된 C++ 코드 블록 내에서 블로킹을 피하기 위해 자체적으로 QEventLoop 을 만들거나 QCoreApplication::processEvents()을 호출하는 것입니다. 이는 신호 처리기나 바인딩에 이벤트 루프가 입력되면 QML 엔진이 다른 바인딩, 애니메이션, 트랜지션 등을 계속 실행하기 때문에 위험합니다. 이러한 바인딩은 예를 들어 이벤트 루프가 포함된 계층 구조를 파괴하는 부작용을 일으킬 수 있습니다.
프로파일링
가장 중요한 팁은 QML ProfilerQt Creator 에 포함된 애플리케이션에서 시간이 어디에 쓰이는지 알면 잠재적으로 존재하는 문제 영역이 아니라 실제로 존재하는 문제 영역에 집중할 수 있습니다. 자세한 내용은 Qt Creator: QML 애플리케이션 프로파일링을 참조하세요.
가장 자주 실행되는 바인딩 또는 애플리케이션이 가장 많은 시간을 소비하는 기능을 파악하면 문제 영역을 최적화할지, 아니면 애플리케이션의 일부 구현 세부 사항을 재설계하여 성능을 개선할지 결정할 수 있습니다. 프로파일링 없이 코드 최적화를 시도하면 성능이 크게 개선되기보다는 매우 미미하게 개선될 가능성이 높습니다.
자바스크립트 코드
대부분의 QML 애플리케이션에는 속성 바인딩 표현식, 함수 및 신호 처리기 등의 형태로 일부 JavaScript 코드가 포함되어 있습니다. 이는 일반적으로 문제가 되지 않습니다. 다음과 같은 고급 도구 덕분에 Qt Quick Compiler와 같은 고급 도구 덕분에 간단한 함수와 바인딩을 매우 빠르게 처리할 수 있습니다. 하지만 불필요한 처리가 실수로 트리거되지 않도록 주의를 기울여야 합니다. 자바스크립트 실행에 대한 QML Profiler 는 자바스크립트 실행 및 트리거 원인에 대한 풍부한 세부 정보를 표시할 수 있습니다.
유형 변환
JavaScript를 사용할 때 발생하는 주요 비용 중 하나는 QML 유형의 프로퍼티에 액세스할 때 기본 C++ 데이터(또는 이에 대한 참조)가 포함된 외부 리소스가 있는 JavaScript 객체가 생성되는 경우가 있다는 것입니다. 대부분의 경우 이 작업은 상당히 저렴하지만 경우에 따라서는 상당히 비쌀 수 있습니다. 크고 복잡한 값 유형 이나 시퀀스 유형을 처리할 때는 주의를 기울여야 합니다. 제자리에서 변경하거나 다른 프로퍼티에 할당할 때마다 QML 엔진에서 복사해야 하기 때문입니다. 이것이 병목 현상이 발생하면 대신 객체 유형을 사용하는 것을 고려하세요. 객체 유형 목록은 QQmlListProperty 을 사용하여 구현되므로 값 유형 목록과 같은 문제가 발생하지 않습니다.
단순한 값 유형 간의 변환은 대부분 저렴합니다. 하지만 예외도 있습니다. 문자열에서 URL을 만들려면 QUrl 인스턴스를 만들어야 하므로 비용이 많이 듭니다.
속성 확인
속성 확인에는 시간이 걸립니다. 일반적으로 조회는 후속 실행에서 훨씬 빠르게 실행되도록 최적화되어 있지만, 가능하면 불필요한 작업을 아예 하지 않는 것이 가장 좋습니다.
다음 예제에서는 자주 실행되는 코드 블록(이 경우 명시적 루프의 내용이지만, 예를 들어 일반적으로 평가되는 바인딩 표현식일 수도 있음)이 있고, 그 안에서 "rect" id와 해당 "color" 속성을 가진 객체를 여러 번 확인합니다:
// bad.qml import QtQuick Item { width: 400 height: 200 Rectangle { id: rect anchors.fill: parent color: "blue" } function printValue(which: string, value: real) { 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"); } }
rect.color 을 검색할 때마다 QML 엔진은 그래야 합니다:
이 작업을 4번 할 필요는 없습니다. 대신 블록에서 공통 기반을 한 번만 확인하면 됩니다:
// good.qml import QtQuick Item { width: 400 height: 200 Rectangle { id: rect anchors.fill: parent color: "blue" } function printValue(which: string, value: real) { 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: string, value: real) { 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; } }
시퀀스 팁
앞서 언급했듯이 값 유형의 시퀀스는 주의해서 다뤄야 합니다.
첫째, 시퀀스 유형은 두 가지 시나리오에서 서로 다른 동작을 보입니다:
- 시퀀스가 QObject 의 Q_PROPERTY 인 경우(이를 참조 시퀀스라고 부릅니다),
- QObject 의 Q_INVOKABLE 함수에서 반환된 경우(이를 복사 시퀀스라고 부릅니다).
참조 시퀀스는 자바스크립트 코드나 원본 객체에서 변경될 때마다 QMetaObject 을 통해 읽고 씁니다. 최적화를 위해 참조 시퀀스(및 참조 값 유형)는 느리게 로드될 수 있습니다. 그런 다음 실제 콘텐츠는 처음 사용될 때만 검색됩니다. 즉, JavaScript에서 시퀀스의 모든 요소의 값을 변경하면 결과가 발생합니다:
복사 시퀀스는 실제 시퀀스가 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"); } }
피해야 할 또 다른 일반적인 패턴은 각 요소를 읽고, 수정하고, 다시 시퀀스 프로퍼티에 쓰는 읽기-수정-쓰기 루프입니다. 이전 예제와 마찬가지로 이 경우 모든 반복에서 QObject 속성을 읽고 쓰게 됩니다:
// 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] = qrealListProperty[j] * 2; } } var t1 = new Date(); console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds"); } }
대신 시퀀스의 수동 복사본을 만들고 복사본을 수정한 다음 결과를 다시 프로퍼티에 할당합니다:
// good.qml import QtQuick import Qt.example SequenceTypeExample { id: root width: 200 height: 200 Component.onCompleted: { var t0 = new Date(); for (var i = 0; i < 500; ++i) { let data = [...qrealListProperty]; for (var j = 0; j < 100; ++j) { data[j] = data[j] * 2; } qrealListProperty = data; } 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 유형
Qt Qml 모델은 ListView 에 데이터를 공급하는 데 사용할 수 있는 ListModel 유형을 제공합니다. 빠른 프로토타이핑에는 유용하지만 대량의 데이터에는 적합하지 않습니다. 필요한 경우 적절한 QAbstractItemModel 을 사용하세요.
워커 스레드 내에서 채우기
ListModel 엘리먼트는 자바스크립트의 (우선순위가 낮은) 워커 스레드에서 채울 수 있습니다. 개발자는 변경 사항을 메인 스레드에 동기화하려면 WorkerScript 내에서 ListModel 에서 sync() 을 명시적으로 호출해야 합니다. 자세한 내용은 WorkerScript 문서를 참조하세요.
WorkerScript 요소를 사용하면 자바스크립트 엔진이 스레드 단위이므로 별도의 자바스크립트 엔진이 생성된다는 점에 유의하세요. 이로 인해 메모리 사용량이 증가합니다. 그러나 여러 개의 WorkerScript 요소는 모두 동일한 작업자 스레드를 사용하므로 애플리케이션에서 이미 하나의 요소를 사용하고 있다면 두 번째 또는 세 번째 WorkerScript 요소 사용으로 인한 메모리 영향은 무시할 수 있습니다. 그러나 반대로 추가 작업자 스크립트는 병렬로 실행되지 않습니다.
동적 역할 사용 금지
ListModel 요소는 주어진 모델에서 각 요소 내의 역할 유형이 최적화 목적으로 안정적이라고 가정합니다. 유형이 요소마다 동적으로 변경될 수 있다면 모델의 성능이 훨씬 나빠질 것입니다.
따라서 동적 입력은 기본적으로 비활성화되어 있으며, 개발자가 모델의 부울 dynamicRoles 속성을 구체적으로 설정하여 동적 입력을 사용하도록 해야 합니다(그에 따른 성능 저하를 감수해야 함). 꼭 필요한 경우가 아니라면 동적 입력을 사용하지 않는 것이 좋습니다.
뷰
뷰 델리게이트는 가능한 한 단순하게 유지해야 합니다. 델리게이트에 필요한 정보를 표시하는 데 필요한 만큼의 QML만 넣어야 합니다. 즉시 필요하지 않은 추가 기능(예: 클릭 시 추가 정보를 표시하는 경우)은 필요할 때까지 만들지 않아야 합니다(향후 지연 초기화에 대한 섹션 참조).
다음 목록은 델리게이트를 디자인할 때 염두에 두어야 할 사항을 잘 요약한 것입니다:
- 델리게이트에 포함된 요소 수가 적을수록 더 빨리 생성할 수 있으므로 뷰를 더 빠르게 스크롤할 수 있습니다.
- 델리게이트의 바인딩 수를 최소한으로 유지하고, 특히 델리게이트 내에서 상대적 위치를 지정할 때는 바인딩 대신 앵커를 사용하세요.
- 델리게이트 내에서 ShaderEffect 요소를 사용하지 마세요.
- 델리게이트에서 클리핑을 활성화하지 마세요.
보기의 cacheBuffer 속성을 설정하여 표시 영역 외부에서 델리게이트의 비동기 생성 및 버퍼링을 허용할 수 있습니다. 사소하지 않고 단일 프레임 내에 생성될 가능성이 낮은 뷰 델리게이트에는 cacheBuffer 을 사용하는 것이 좋습니다.
cacheBuffer 은 추가 델리게이트를 인메모리에 유지한다는 점을 명심하세요. 따라서 cacheBuffer 활용을 통해 얻은 값과 추가 메모리 사용량의 균형을 맞춰야 합니다. cacheBuffer 활용으로 인한 메모리 사용량 증가로 인해 드물지만 스크롤 시 프레임 속도가 저하될 수 있으므로 개발자는 벤치마킹을 통해 자신의 사용 사례에 가장 적합한 값을 찾아야 합니다.
추가적인 성능 향상을 위해 뷰에서 항목 재사용을 활성화하는 것도 고려해 보세요. 자세한 내용은 목록 보기에 항목 재사용하기 및 테이블 보기 및 트리 보기에 항목 재사용하기를 참조하세요.
시각 효과
Qt Quick 에는 개발자와 디자이너가 매우 매력적인 사용자 인터페이스를 만들 수 있는 여러 기능이 포함되어 있습니다. 시각 효과뿐만 아니라 유동성 및 동적 전환은 애플리케이션에서 매우 효과적으로 사용될 수 있지만 QML의 일부 기능은 성능에 영향을 미칠 수 있으므로 사용할 때 약간의 주의를 기울여야 합니다.
애니메이션
일반적으로 프로퍼티에 애니메이션을 적용하면 해당 프로퍼티를 참조하는 모든 바인딩이 다시 평가됩니다. 일반적으로는 이렇게 하는 것이 바람직하지만 다른 경우에는 애니메이션을 수행하기 전에 바인딩을 비활성화한 다음 애니메이션이 완료되면 바인딩을 다시 할당하는 것이 더 나을 수 있습니다.
애니메이션 중에는 JavaScript를 실행하지 마세요. 예를 들어, x 속성 애니메이션의 각 프레임에 대해 복잡한 자바스크립트 표현식을 실행하는 것은 피해야 합니다.
스크립트 애니메이션은 메인 스레드에서 실행되므로 완료하는 데 너무 오래 걸리면 프레임이 건너뛸 수 있으므로 개발자는 특히 주의해서 사용해야 합니다.
파티클
파티클 Qt Quick Particles 모듈을 사용하면 아름다운 파티클 효과를 사용자 인터페이스에 매끄럽게 통합할 수 있습니다. 그러나 플랫폼마다 그래픽 하드웨어 기능이 다르므로 파티클 모듈은 하드웨어가 지원하는 범위로 매개변수를 제한할 수 없습니다. 렌더링하려는 파티클이 많을수록(그리고 파티클의 크기가 클수록) 60 FPS로 렌더링하려면 그래픽 하드웨어가 더 빨라져야 합니다. 더 많은 파티클에 영향을 주려면 더 빠른 CPU가 필요합니다. 따라서 대상 플랫폼에서 모든 파티클 효과를 신중하게 테스트하여 60 FPS로 렌더링할 수 있는 파티클의 수와 크기를 보정하는 것이 중요합니다.
파티클 시스템을 사용하지 않을 때(예: 보이지 않는 요소에서) 비활성화하면 불필요한 시뮬레이션을 피할 수 있습니다.
자세한 내용은 파티클 시스템 퍼포먼스 가이드를 참조하세요.
엘리먼트 수명 제어하기
애플리케이션을 단일 QML 파일에 포함된 간단한 모듈식 컴포넌트로 분할하면 애플리케이션 시작 시간을 단축하고 메모리 사용량을 더 효과적으로 제어할 수 있으며, 애플리케이션에서 활성 상태이지만 보이지 않는 요소의 수를 줄일 수 있습니다.
지연 초기화
QML 엔진은 컴포넌트의 로딩 및 초기화로 인해 프레임이 건너뛰지 않도록 하기 위해 몇 가지 까다로운 작업을 수행합니다. 그러나 필요하지 않은 작업을 하지 않고 필요한 작업이 될 때까지 지연시키는 것보다 시작 시간을 줄이는 더 좋은 방법은 없습니다. 이를 위해 Loader.
로더 사용
로더는 컴포넌트를 동적으로 로드 및 언로드할 수 있는 요소입니다.
- 로더의 "active" 속성을 사용하면 필요할 때까지 초기화를 지연시킬 수 있습니다.
- 오버로드된 버전의 "setSource()" 함수를 사용하여 초기 속성 값을 제공할 수 있습니다.
- Loader asynchronous 속성을 true로 설정하면 컴포넌트가 인스턴스화되는 동안 유동성이 향상될 수도 있습니다.
사용하지 않는 요소 삭제
보이지 않는 요소의 자식이기 때문에 보이지 않는 요소(예: 탭 위젯에서 첫 번째 탭이 표시되는 동안 두 번째 탭)는 대부분의 경우 느리게 초기화해야 하며, 더 이상 사용하지 않을 때는 삭제하여 렌더링, 애니메이션, 속성 바인딩 평가 등 활성 상태로 유지하는 데 드는 지속적인 비용을 방지해야 합니다.
로더 요소로 로드된 항목은 로더의 "source" 또는 "sourceComponent" 속성을 재설정하여 해제할 수 있으며, 다른 항목은 해당 항목에 대해 destroy()를 호출하여 명시적으로 해제할 수 있습니다. 경우에 따라서는 항목을 활성 상태로 유지해야 할 수도 있는데, 이 경우 최소한 보이지 않게 만들어야 합니다.
활성화되어 있지만 보이지 않는 요소에 대한 자세한 내용은 렌더링에 대한 다음 섹션을 참조하세요.
렌더링
에서 렌더링에 사용되는 씬 그래프 Qt Quick 에서 렌더링에 사용되는 장면 그래프를 사용하면 매우 동적이고 애니메이션이 적용된 사용자 인터페이스를 60 FPS로 원활하게 렌더링할 수 있습니다. 그러나 렌더링 성능을 크게 저하시킬 수 있는 몇 가지 요소가 있으므로 개발자는 가능한 한 이러한 함정을 피하도록 주의해야 합니다.
클리핑
클리핑은 기본적으로 비활성화되어 있으며 필요한 경우에만 활성화해야 합니다.
클리핑은 시각적 효과이지 최적화가 아닙니다. 렌더러의 복잡성을 줄이는 것이 아니라 오히려 증가시킵니다. 클리핑이 활성화되면 항목은 자신의 그림뿐만 아니라 하위 항목의 그림도 경계 사각형에 클리핑됩니다. 이렇게 하면 렌더러가 요소의 그리기 순서를 자유롭게 재지정할 수 없으므로 최적이 아닌 최상의 씬 그래프 순회가 발생합니다.
델리게이트 내부 클리핑은 특히 좋지 않으므로 어떤 대가를 치르더라도 피해야 합니다.
오버드로잉 및 보이지 않는 요소
다른 (불투명한) 요소로 완전히 가려진 요소가 있는 경우 해당 요소의 "표시" 속성을 false 로 설정하는 것이 가장 좋습니다. 그렇지 않으면 불필요하게 그려지게 됩니다.
마찬가지로 보이지 않지만(예: 탭 위젯에서 첫 번째 탭은 표시되지만 두 번째 탭은 보이지 않는 경우) 시작 시 초기화해야 하는 요소(예: 탭이 활성화된 경우에만 두 번째 탭을 인스턴스화하는 데 비용이 너무 오래 걸리는 경우)는 그리는 비용을 피하기 위해 "visible" 속성을 false 으로 설정해야 합니다(앞서 설명한 대로 여전히 활성화되어 있으므로 애니메이션 또는 바인딩 평가 비용이 발생하지만).
반투명 대 불투명
일반적으로 불투명 콘텐츠는 반투명 콘텐츠보다 그리는 속도가 훨씬 빠릅니다. 그 이유는 반투명 콘텐츠는 블렌딩이 필요하고 렌더러가 불투명 콘텐츠를 더 잘 최적화할 수 있기 때문입니다.
반투명 픽셀이 하나 있는 이미지는 대부분 불투명하더라도 완전히 반투명한 것으로 처리됩니다. 가장자리가 투명한 BorderImage 의 경우에도 마찬가지입니다.
셰이더
ShaderEffect 유형을 사용하면 오버헤드가 거의 없는 Qt Quick 애플리케이션에 GLSL 코드를 인라인으로 배치할 수 있습니다. 그러나 렌더링된 모양의 모든 픽셀에 대해 조각 프로그램이 실행되어야 한다는 점을 인식하는 것이 중요합니다. 저사양 하드웨어에 배포하고 셰이더가 많은 양의 픽셀을 커버하는 경우 성능 저하를 방지하기 위해 조각 셰이더를 몇 가지 명령으로 유지해야 합니다.
GLSL로 작성된 셰이더를 사용하면 복잡한 변환과 시각 효과를 작성할 수 있지만 주의해서 사용해야 합니다. ShaderEffectSource 을 사용하면 씬이 그려지기 전에 FBO로 미리 렌더링됩니다. 이 추가 오버헤드는 상당히 비쌀 수 있습니다.
메모리 할당 및 수집
애플리케이션이 할당할 메모리 양과 메모리 할당 방식은 매우 중요한 고려 사항입니다. 메모리가 제한된 디바이스에서 메모리 부족 상태에 대한 명백한 우려 외에도 힙에 메모리를 할당하는 것은 계산 비용이 상당히 많이 드는 작업이며 특정 할당 전략은 페이지 전체에 걸쳐 데이터 조각화를 증가시킬 수 있습니다. 자바스크립트는 자동으로 가비지 수집되는 관리형 메모리 힙을 사용하며, 여기에는 몇 가지 장점이 있지만 몇 가지 중요한 의미도 있습니다.
QML로 작성된 애플리케이션은 C++ 힙과 자동 관리되는 JavaScript 힙의 메모리를 모두 사용합니다. 애플리케이션 개발자는 성능을 극대화하기 위해 각각의 미묘한 차이를 알고 있어야 합니다.
QML 애플리케이션 개발자를 위한 팁
이 섹션에 포함된 팁과 제안은 가이드라인일 뿐이며 모든 상황에 적용되지 않을 수 있습니다. 가능한 한 최선의 결정을 내리기 위해 경험적 지표를 사용하여 애플리케이션을 신중하게 벤치마킹하고 분석해야 합니다.
구성 요소를 느리게 인스턴스화 및 초기화하기
애플리케이션이 여러 개의 보기(예: 여러 개의 탭)로 구성되어 있지만 한 번에 하나만 필요한 경우 지연 인스턴스화를 사용하여 주어진 시간에 할당해야 하는 메모리의 양을 최소화할 수 있습니다. 자세한 내용은 지연 초기화에 대한 이전 섹션을 참조하세요.
사용하지 않는 개체 삭제
컴포넌트를 지연 로드하거나 JavaScript 표현식 중에 동적으로 객체를 생성하는 경우 자동 가비지 수집을 기다리는 것보다 수동으로 destroy() 삭제하는 것이 더 나은 경우가 많습니다. 자세한 내용은 엘리먼트 수명 제어에 대한 이전 섹션을 참조하세요.
가비지 수집기를 수동으로 호출하지 않기
대부분의 경우 가비지 수집기를 수동으로 호출하면 상당한 시간 동안 GUI 스레드가 차단되므로 가비지 수집기를 수동으로 호출하는 것은 현명하지 않습니다. 이로 인해 프레임이 건너뛰거나 애니메이션이 끊기는 현상이 발생할 수 있으므로 어떤 대가를 치르더라도 피해야 합니다.
가비지 수집기를 수동으로 호출하는 것이 허용되는 경우도 있지만(이에 대해서는 다음 섹션에서 자세히 설명합니다), 대부분의 경우 가비지 수집기를 호출하는 것은 불필요하고 비생산적입니다.
동일한 암시적 유형을 여러 개 정의하지 마세요.
QML 요소에 QML에 정의된 사용자 정의 프로퍼티가 있는 경우, 해당 프로퍼티는 자체 암시적 유형이 됩니다. 이에 대해서는 다음 섹션에서 자세히 설명합니다. Component 에 여러 개의 동일한 암시적 유형이 정의되어 있으면 일부 메모리가 낭비됩니다. 이러한 상황에서는 일반적으로 재사용할 수 있는 새 컴포넌트를 명시적으로 정의하는 것이 좋습니다. 이러한 경우 component 키워드를 사용하여 인라인 컴포넌트를 정의하는 것을 고려해 보세요.
사용자 정의 속성을 정의하는 것은 종종 성능 최적화(예: 필요하거나 재평가되는 바인딩의 수를 줄이기 위해)에 도움이 되거나 컴포넌트의 모듈성 및 유지보수성을 향상시킬 수 있습니다. 이러한 경우 사용자 정의 프로퍼티를 사용하는 것이 좋습니다. 그러나 새 유형이 두 번 이상 사용되는 경우 메모리를 절약하기 위해 자체 컴포넌트(인라인 또는 .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" } }
앞의 예에서 사각형 r0 및 r1 에는 사용자 정의 속성이 없으므로 JavaScript 및 QML 엔진은 두 사각형이 모두 같은 유형으로 간주합니다. 즉, r0 과 r1 은 모두 명시적으로 정의된 Rectangle 유형으로 간주됩니다. 직사각형 r2, r3 및 r4 은 각각 사용자 정의 속성을 가지며 각각 다른 (암시적) 유형으로 간주됩니다. r3 과 r4 은 동일한 속성 정보를 가지고 있더라도 인스턴스인 컴포넌트에서 사용자 정의 속성이 선언되지 않았기 때문에 각각 다른 유형으로 간주됩니다.
r3 와 r4 이 모두 RectangleWithString 컴포넌트의 인스턴스이고 해당 컴포넌트 정의에 customProperty 라는 문자열 프로퍼티의 선언이 포함되어 있다면 r3 와 r4 는 동일한 유형으로 간주됩니다(즉, 자체 암시적 유형을 정의하는 것이 아니라 RectangleWithString 유형의 인스턴스가 되는 것입니다).
심층적인 메모리 할당 고려 사항
메모리 할당 또는 성능 트레이드오프에 관한 결정을 내릴 때마다 CPU 캐시 성능, 운영 체제 페이징 및 JavaScript 엔진 가비지 수집의 영향을 염두에 두는 것이 중요합니다. 최상의 솔루션을 선택할 수 있도록 잠재적인 솔루션을 신중하게 벤치마킹해야 합니다.
어떤 일반적인 가이드라인도 컴퓨터 과학의 기본 원리에 대한 확실한 이해와 애플리케이션 개발자가 개발 중인 플랫폼의 구현 세부 사항에 대한 실질적인 지식을 대체할 수 없습니다. 또한, 트레이드오프 결정을 내릴 때 아무리 이론적인 계산을 해도 좋은 벤치마크와 분석 도구 세트를 대체할 수는 없습니다.
조각화
조각화는 C++ 개발 문제입니다. 애플리케이션 개발자가 C++ 유형이나 플러그인을 정의하지 않는다면 이 섹션을 무시해도 됩니다.
시간이 지남에 따라 애플리케이션은 메모리의 많은 부분을 할당하고, 해당 메모리에 데이터를 쓰고, 일부 데이터 사용을 마친 후에는 메모리의 일부를 해제합니다. 이로 인해 "사용 가능한" 메모리가 비연속적인 청크에 위치하게 되어 다른 애플리케이션이 사용할 수 있도록 운영 체제로 반환할 수 없게 될 수 있습니다. 또한 '살아있는' 데이터가 물리적 메모리의 여러 페이지에 분산되어 있을 수 있으므로 애플리케이션의 캐싱 및 액세스 특성에도 영향을 미칩니다. 이로 인해 운영 체제가 강제로 스왑을 수행하여 파일 시스템 I/O가 발생할 수 있으며, 이는 상대적으로 매우 느린 작업입니다.
조각화는 풀 할당기(및 기타 인접 메모리 할당기)를 활용하거나, 객체 수명을 신중하게 관리하여 한 번에 할당되는 메모리 양을 줄이거나, 주기적으로 캐시를 정리 및 재구축하거나, 가비지 컬렉션을 통해 메모리 관리 런타임(JavaScript 등)을 활용함으로써 피할 수 있습니다.
가비지 컬렉션
자바스크립트는 가비지 컬렉션을 제공합니다. 자바스크립트 힙에 할당된 메모리(C++ 힙이 아닌)는 자바스크립트 엔진이 소유합니다. 엔진은 자바스크립트 힙에서 참조되지 않은 모든 데이터를 주기적으로 수집합니다.
가비지 컬렉션의 의미
가비지 컬렉션에는 장단점이 있습니다. 객체 수명을 수동으로 관리하는 것이 덜 중요하다는 뜻입니다. 하지만 애플리케이션 개발자가 통제할 수 없는 시점에 자바스크립트 엔진이 잠재적으로 오래 지속될 수 있는 작업을 시작할 수 있다는 의미이기도 합니다. 애플리케이션 개발자가 자바스크립트 힙 사용을 신중하게 고려하지 않으면 가비지 컬렉션의 빈도와 지속 시간이 애플리케이션 환경에 부정적인 영향을 미칠 수 있습니다. Qt 6.8부터 가비지 수집기는 증분형이므로 가비지 수집 주기는 짧아지지만 중단이 더 많이 발생할 수 있습니다.
가비지 컬렉터 수동 호출하기
QML로 작성된 애플리케이션은 (대부분) 어느 단계에서 가비지 수집을 수행해야 합니다. 가비지 수집은 자바스크립트 엔진이 자체 일정에 따라 자동으로 트리거하지만, 애플리케이션 개발자가 가비지 수집기를 언제 수동으로 호출할지 결정하는 것이 더 나은 경우도 있습니다(보통은 그렇지 않지만).
애플리케이션 개발자는 애플리케이션이 상당한 시간 동안 유휴 상태가 되는 시기를 가장 잘 파악하고 있을 가능성이 높습니다. QML 애플리케이션이 자바스크립트 힙 메모리를 많이 사용하여 특히 성능에 민감한 작업(예: 목록 스크롤, 애니메이션 등) 중에 가비지 수집 주기가 정기적으로 중단되는 경우, 애플리케이션 개발자는 활동이 없는 기간 동안 가비지 수집기를 수동으로 호출하는 것이 좋을 수 있습니다. 유휴 기간은 가비지 수집을 수행하기에 이상적인데, 활동 중에 가비지 수집기를 호출할 경우 발생할 수 있는 사용자 경험 저하(프레임 건너뛰기, 애니메이션 끊김 등)를 사용자가 알아차리지 못하기 때문입니다.
가비지 수집기는 자바스크립트 내에서 gc() 을 호출하여 수동으로 호출할 수 있습니다. 이렇게 하면 증분이 아닌 전체 수집 주기가 수행되므로 완료하는 데 수백 밀리초에서 천 밀리초 이상 걸릴 수 있으므로 가능하면 피해야 합니다.
메모리와 성능의 트레이드 오프
일부 상황에서는 처리 시간 단축을 위해 메모리 사용량 증가를 절충할 수 있습니다. 예를 들어, 타이트 루프에서 사용되는 심볼 조회 결과를 JavaScript 표현식의 임시 변수에 캐싱하면 해당 표현식을 평가할 때 성능이 크게 향상되지만 임시 변수를 할당해야 합니다. 위의 경우와 같이 이러한 절충안이 합리적인 경우도 있지만(거의 항상 합리적인 경우), 다른 경우에는 시스템의 메모리 압박을 피하기 위해 처리 시간이 약간 더 오래 걸리도록 허용하는 것이 더 나을 수도 있습니다.
어떤 경우에는 메모리 사용량 증가로 인한 영향이 극심할 수 있습니다. 일부 상황에서는 성능 향상을 위해 메모리 사용량을 줄이면 페이지 쓰레시 또는 캐시 쓰레시가 증가하여 성능이 크게 저하될 수 있습니다. 주어진 상황에서 어떤 솔루션이 가장 적합한지 결정하기 위해서는 항상 트레이드 오프의 영향을 신중하게 벤치마킹해야 합니다.
캐시 성능과 메모리 시간 트레이드오프에 대한 자세한 내용은 다음 문서를 참조하세요:
- 울리히 드레퍼의 훌륭한 글: "모든 프로그래머가 메모리에 대해 알아야 할 것", https://people.freebsd.org/~lstewart/articles/cpumemory.pdf.
- C++ 애플리케이션 최적화에 관한 아그너 포그의 훌륭한 매뉴얼: http://www.agner.org/optimize/.
빠른 부팅 및 시작 최적화
빠른 부팅을 위해 Qt Quick 애플리케이션을 최적화한 실제 경험을 바탕으로 다음 모범 사례를 고려하세요:
- 애플리케이션을 처음부터 빠르게 시작하도록 설계하세요. 사용자에게 무엇을 보여줄지 먼저 생각하세요.
- 를 사용하여 QML Profiler 를 사용하여 시작 시 병목 현상을 파악하세요.
- 체인 로딩을 사용하세요. CPU의 코어 수만큼만 loaders 을 실행하세요(예: 2개의 코어: 2개의 로더가 동시에 실행됨).
- 첫 번째 loader 는 일부 콘텐츠가 즉시 표시되도록 비동기식이어서는 안 됩니다. 그 다음에 비동기 로더를 트리거하세요.
- 필요한 경우에만 백엔드 서비스에 연결하세요.
- 필요할 때 가져오는 QML 모듈을 만듭니다. 지연 로드된 모듈과 유형을 사용하면 필요에 따라 애플리케이션에서 중요하지 않은 서비스를 사용할 수 있습니다.
- 옵티피엔지 같은 도구를 사용해 PNG/JPG 이미지를 최적화하세요.
- 버텍스의 양을 줄이고 보이지 않는 부분을 제거하여 3D 모델을 최적화하세요.
- glTF를 사용하여 3D 모델 로딩을 최적화합니다.
- 클립과 불투명도는 성능에 영향을 줄 수 있으므로 사용을 제한합니다.
- GPU 한계를 측정하고 UI를 디자인할 때 이를 고려합니다. 자세한 내용은 프레임 캡처 및 성능 프로파일링을 참조하세요.
- 사용 Qt Quick Compiler 를 사용하여 QML 파일을 사전 컴파일합니다.
- 아키텍처에 정적 링크가 가능한지 조사합니다.
- 명령형 시그널 핸들러 대신 선언적 바인딩을 사용하도록 노력하세요.
- 속성 바인딩을 단순하게 유지하세요. 일반적으로 QML 코드를 간단하고 재미있고 읽기 쉽게 유지하세요. 좋은 성능이 뒤따릅니다.
- 생성 시간이 문제가 된다면 복잡한 컨트롤을 이미지나 셰이더로 대체하세요.
하지 마세요:
- QML을 과도하게 사용하세요. QML을 사용하더라도 모든 것을 QML로 처리할 필요는 없습니다.
- main.cpp에서 모든 것을 초기화하세요.
- 필요한 모든 인터페이스를 포함하는 큰 싱글톤을 만드세요.
- ListView 또는 다른 보기에 대한 복잡한 델리게이트를 만듭니다.
- 꼭 필요한 경우가 아니면 클립을 사용하세요.
- 로더를 과도하게 사용하는 일반적인 함정에 빠지지 마세요. Loader 는 애플리케이션 페이지와 같이 큰 것을 지연 로드하는 데는 좋지만 간단한 것을 로드하는 데는 너무 많은 오버헤드를 유발합니다. 로더는 모든 것의 속도를 높여주는 흑마법이 아닙니다. QML 컨텍스트가 추가된 추가 항목일 뿐입니다.
이러한 관행은 특히 임베디드 디바이스에서 1초 미만의 시작 시간과 원활한 사용자 경험을 달성하는 데 도움이 됩니다.
© 2026 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.