더 나은 성능을 얻기 위해 임베디드 소프트웨어의 기능을 작성할 때 가장 좋은 방법은 무엇입니까? [닫은]


13

마이크로 컨트롤러 용 라이브러리 중 일부를 보았으며 그 기능은 한 번에 하나씩 수행합니다. 예를 들면 다음과 같습니다.

void setCLK()
{
    // Code to set the clock
}

void setConfig()
{
    // Code to set the config
}

void setSomethingElse()
{
   // 1 line code to write something to a register.
}

그런 다음 함수를 포함하는이 1 행 코드를 사용하여 다른 목적을 제공하는 다른 함수를 사용하십시오. 예를 들면 다음과 같습니다.

void initModule()
{
   setCLK();
   setConfig();
   setSomethingElse();
}

확실하지는 않지만이 방법으로 함수를 호출하거나 종료 할 때마다 점프에 대한 더 많은 호출을 만들고 리턴 주소를 쌓는 오버 헤드를 생성 할 것이라고 생각합니다. 그러면 프로그램이 느려질 것입니다.

나는 수색하고 어디에서나 프로그래밍의 엄지 손가락 규칙은 함수가 하나의 작업 만 수행해야한다는 것입니다.

따라서 시계를 설정하는 InitModule 함수 모듈을 직접 작성하면 원하는 구성을 추가하고 함수를 호출하지 않고 다른 작업을 수행합니다. 임베디드 소프트웨어를 작성할 때 나쁜 접근 방식입니까?


편집 2 :

  1. 마치 많은 사람들이 프로그램을 최적화하려고하는 것처럼이 질문을 이해 한 것 같습니다. 아니요, 할 의사가 없습니다 . 컴파일러가 나보다 항상 더 좋을 것이기 때문에 컴파일러가 그것을하도록 내버려두고 있습니다.

  2. 초기화 코드를 나타내는 예제를 선택한 것에 대한 모든 책임 . 초기화 목적으로 만들어진 함수 호출에 대해서는 의문의 여지가 없습니다. 내 질문은 무한 루프 내에서 실행 하는 특정 작업을 여러 줄의 작은 기능으로 나눠서 ( 인라인은 문제 가되지 않습니다) 중첩 된 함수없이 긴 함수를 작성하는 것보다 이점이 있습니까?

@Jonk답변에 정의 된 가독성을 고려하십시오 .


28
합리적인 컴파일러가 맹목적으로 작성된 코드를 바이너리로 작성된대로 맹목적으로 전환한다고 생각하면 매우 모욕적입니다. 대부분의 최신 컴파일러는 루틴이 더 잘 인라인 될 때와 레지스터 대 RAM 위치를 사용하여 변수를 보유해야하는 경우를 식별하는 데 능숙합니다. 두 가지 최적화 규칙을 따르십시오. 1) 최적화하지 마십시오. 2) 아직 최적화하지 마십시오 . 코드를 읽을 수 있고 유지 관리 할 수있게하고 작업 시스템을 프로파일 링 한 후에 만 ​​최적화하십시오.
akohlsmith

10
@akohlsmith IIRC 최적화 의 세 가지 규칙은 다음과 같습니다. 1)하지 마십시오! 2) 아니요. 당신이 경우 3) 최적화에만 다음 다음, 첫 번째 프로필과 해야 - Michael_A._Jackson을
esoterik

3
"초기 최적화는 프로그래밍에서 모든 악 (또는 적어도 대부분)의 근원"이라는 것을 기억하십시오.- Knuth
Mawg는 모니카 복원

1
@Mawg : 작동 단어가 조숙 합니다. (그 논문의 다음 단락에서 설명 하듯이 말 그대로 다음 문장은 "그래서 우리는 그 중요한 3 %의 기회를 넘겨서는 안됩니다.") 필요할 때까지 최적화하지 마십시오. 당신이 프로파일 링 할 무언가가있을 때까지 비트-그러나 또한 예를 들어 끔찍하게 잘못된 도구를 사용하여 비관에 관여하지 마십시오.
cHao

1
@Mawg 나는 단어를 언급하지 않았기 때문에 최적화와 관련된 답변 / 피드백을받는 이유를 모르겠습니다. 문제는 임베디드 프로그래밍에서 함수를 작성하여 더 나은 성능을 얻는 방법에 대한 것입니다.
MaNyYaCk

답변:


28

의심 할 여지없이, 예제에서 코드는 시작시 한 번만 실행되므로 성능은 중요하지 않습니다.

내가 사용하는 규칙 : 가능한 한 코드를 읽을 수 있도록 작성하고 컴파일러가 마술을 제대로 수행하지 않는 경우에만 최적화를 시작하십시오.

ISR에서 함수 호출 비용은 스토리지 및 타이밍 측면에서 시작 중 함수 호출 비용과 동일 할 수 있습니다. 그러나 해당 ISR 중 타이밍 요구 사항이 훨씬 더 중요 할 수 있습니다.

또한 다른 사람들이 이미 알 수 있듯이 함수 호출의 비용 및 '비용'의 의미는 플랫폼, 컴파일러, 컴파일러 최적화 설정 및 응용 프로그램 요구 사항에 따라 다릅니다. 8051과 cortex-m7, 심박 조율기와 전등 스위치 사이에는 큰 차이가 있습니다.


6
IMO 두 번째 단락은 굵게 표시되어야합니다. 올바른 알고리즘과 데이터 구조를 선택하는 데 아무런 문제가 없지만 실제로 병목 현상이 조기에 최적화되어 피해야한다는 것을 알지 못하면 함수 호출 오버 헤드 에 대해 걱정할 필요가 없습니다.
기금 모니카의 소송

11

한 줄의 코드를 함수 또는 서브 루틴으로 감싸서 내가 생각할 수있는 이점은 없습니다 (그러나 JasonS에 대한 참고 사항 참조). 함수 이름을 "읽기 쉬운"것으로 지정할 수있는 경우를 제외하고. 그러나 당신은 라인을 주석 처리 할 수 ​​있습니다. 함수에 코드 줄을 정리하면 코드 메모리, 스택 공간 및 실행 시간이 들기 때문에 대부분 비생산적인 것 같습니다 . 교육 상황에서? 말이 될 수도 있습니다. 그러나 그것은 학생들의 수업, 사전 준비, 커리큘럼 및 교사에 달려 있습니다. 대부분 좋은 생각이 아니라고 생각합니다. 그러나 그것은 나의 의견이다.

결론에 이르게됩니다. 당신의 광범위한 질문 영역은 수십 년 동안 논쟁의 문제였으며 오늘날까지도 여전히 논쟁의 문제입니다. 따라서 적어도 귀하의 질문을 읽을 때 (당신이 질문 한대로) 의견 기반의 질문 인 것 같습니다.

상황에 대해 더 자세하게 설명하고 기본 목표로 세운 목표를주의 깊게 설명한다면, 그것은 의견에 근거한 것에서 벗어날 수 있습니다. 측정 도구를 잘 정의할수록 대답이 더 객관적 일 수 있습니다.


일반적으로 모든 코딩에 대해 다음을 수행하려고합니다 . (아래에서는 목표를 달성하는 모든 접근 방식을 비교한다고 가정합니다. 분명히 필요한 작업을 수행하지 못하는 코드는 작성 방법에 관계없이 성공한 코드보다 나쁩니다.)

  1. 코드를 읽는 다른 사람이 코딩 프로세스에 어떻게 접근하는지 이해하도록 접근 방식에 일관성을 유지하십시오. 일관성이없는 것은 아마도 최악의 범죄 일 것입니다. 그것은 다른 사람들을 어렵게 할뿐만 아니라 몇 년 후 코드로 돌아가는 것을 어렵게 만듭니다.
  2. 가능한 한, 순서에 관계없이 다양한 기능 섹션의 초기화를 수행 할 수 있도록 사물을 배열하십시오. 순서가 필요한 경우, 관련성이 높은 두 개의 하위 기능 이 밀접하게 결합 되어 있으면 두 가지 모두에 대해 단일 초기화를 고려하여 피해를주지 않고 순서를 다시 지정할 수 있습니다. 이것이 가능하지 않은 경우 초기화 순서 요구 사항을 문서화하십시오.
  3. 캡슐화 지식가능한 경우 정확히 한 곳에 을 하십시오. 상수는 코드의 모든 곳에서 복제되어서는 안됩니다. 일부 변수를 해결하는 방정식은 한 곳에만 존재해야합니다. 등등. 다양한 위치에서 필요한 동작을 수행하는 일부 행 집합을 복사하여 붙여 넣는 경우 해당 지식을 한 곳에서 캡처하여 필요한 곳에서 사용하는 방법을 고려하십시오. 특정 방식으로 걸어해야 트리 구조를 가지고 예를 들어, 할 수 없습니다 은 트리 노드를 순환해야하는 모든 장소에서 트리 워킹 코드를 복제합니다. 대신, 트리 워킹 방법을 한 곳에서 캡처하여 사용하십시오. 이런 식으로, 나무가 바뀌고 보행 방법이 바뀌면 걱정할 곳이 하나 밖에 없으며 나머지 코드는 모두 "정상 작동"합니다.
  4. 모든 루틴을 거대한 평평한 종이에 펼치고 화살표가 다른 루틴에 의해 호출되는 것처럼 연결하면 모든 응용 프로그램에서 화살표가 많은 루틴의 "클러스터"가 있음을 알 수 있습니다 그들 사이에 있지만 그룹 바깥에 몇 개의 화살 만 있습니다. 그래서이있을 것이다 자연 밀접하게 결합 된 루틴의 경계와 밀접하게 결합 된 루틴의 다른 그룹과 느슨하게 결합 된 연결을. 이 사실을 사용하여 코드를 모듈로 구성하십시오. 이렇게하면 코드의 복잡성이 크게 줄어 듭니다.

위의 내용은 일반적으로 모든 코딩에 적용됩니다. 매개 변수, 로컬 또는 정적 전역 변수 등의 사용에 대해서는 논의하지 않았습니다. 그 이유는 임베디드 프로그래밍의 경우 응용 프로그램 공간이 종종 극도로 매우 중요한 새로운 제약 조건을 배치하기 때문에 모든 임베디드 응용 프로그램에 대해 논의하지 않고 모든 제약 조건을 논의 할 수 없기 때문입니다. 그리고 어쨌든 여기서 일어나지 않습니다.

이러한 제약 조건은 다음 중 하나 이상일 수 있습니다.

  • 최소 RAM과 거의 I / O 핀 수를 갖지 않는 매우 원시적 인 MCU를 요구하는 심각한 비용 제한. 이를 위해 완전히 새로운 규칙이 적용됩니다. 예를 들어 코드 공간이 많지 않으므로 어셈블리 코드로 작성해야 할 수 있습니다. 로컬 변수를 사용하면 비용이 많이 들고 시간이 많이 걸리므로 정적 변수 만 사용해야 할 수도 있습니다. 서브 루틴 리턴 주소를 저장할 하드웨어 레지스터가 4 개뿐이므로 (예 : 일부 Microchip PIC 부품) 서브 루틴을 과도하게 사용하지 않아야 할 수도 있습니다. 따라서 코드를 극적으로 "평평하게"해야 할 수도 있습니다. 기타.
  • 대부분의 MCU를 시작하고 종료하기 위해 정교하게 제작 된 코드가 필요하고 최대 속도로 실행할 때 코드 실행 시간에 심각한 제한이있는 심각한 전원 제한. 다시 말하지만 때때로 일부 어셈블리 코딩이 필요할 수 있습니다.
  • 심각한 타이밍 요구 사항. 예를 들어, 오픈 드레인 0의 전송이 정확히 1의 전송과 동일한 수의 사이클을 취해야하는지 확인해야 할 때가 있습니다. 그리고 동일한 라인을 샘플링해야했습니다. 이 타이밍에 대한 정확한 상대 단계. 이것은 여기서 C를 사용할 수 없다는 것을 의미했습니다. 보장 할 수있는 유일한 방법은 어셈블리 코드를 신중하게 작성하는 것입니다. (그리고 심지어 모든 ALU 디자인에서 항상 그런 것은 아닙니다.)

등등. 생명에 중요한 의료 기기의 배선 코드는 전 세계에 있습니다.

여기서 결론은 임베디드 코딩이 종종 자유롭지 않다는 것입니다. 워크 스테이션 에서처럼 코딩 할 수 있습니다. 매우 다양한 제약 조건에 대한 심각하고 경쟁적인 이유가 종종 있습니다. 그리고 이것들은 더 전통적인 답변 과 주식 답변 에 강력하게 반대 할 수 있습니다 .


가독성과 관련하여 읽을 때 배울 수있는 일관된 방식으로 작성된 코드는 읽을 수있는 것으로 나타났습니다. 그리고 코드를 난독 화하려는 의도적 인 시도가없는 곳. 더 이상 필요하지 않습니다.

읽을 수있는 코드는 매우 효율적일 수 있으며 위에서 언급 한 위의 모든 요구 사항을 충족 할 수 있습니다 . 가장 중요한 것은 작성하는 각 코드 줄이 코드를 작성할 때 어셈블리 또는 기계 수준에서 생성되는 내용을 완전히 이해한다는 것입니다. C ++는 많은 상황이 있기 때문에 여기 프로그래머에 심각한 부담을 동일한 C ++ 코드의 조각이 실제로 생성 다른 매우 다른 공연을 기계 코드의 조각이. 그러나 C는 일반적으로 "보이는 것이 얻는 것"입니다. 따라서 그 점에서 더 안전합니다.


JasonS 당 편집 :

1978 년부터 C를 사용했고 1987 년부터 C ++을 사용해 왔으며 메인 프레임, 미니 컴퓨터 및 (대부분) 임베디드 응용 프로그램 모두에 대해 많은 경험을 가지고 있습니다.

Jason은 '인라인'을 수정 자로 사용하는 것에 대한 의견을 제시합니다. (내 관점에서 볼 때 이것은 C 및 C ++를 사용하여 내 인생의 절반 이상을 위해 존재하지 않았기 때문에 비교적 "새로운"기능입니다.) 인라인 함수를 사용하면 실제로 이러한 호출을 할 수 있습니다 (한 줄의 경우에도 코드) 아주 실용적입니다. 그리고 컴파일러가 적용 할 수있는 타이핑 때문에 매크로를 사용하는 것보다 가능하면 훨씬 좋습니다.

그러나 한계도 있습니다. 첫 번째는 "힌트를 가져 오기 위해"컴파일러에 의존 할 수 없다는 것입니다. 그럴 수도 있고 아닐 수도 있습니다. 그리고 힌트를 얻지 않는 좋은 이유가 있습니다. (함수의 주소를 가지고가는 경우에 명백한 예를 들어,이 요구 함수의 인스턴스 및 ... 전화를 필요로 전화를 걸 수있는 주소의 사용을. 코드는 다음 인라인 할 수 없습니다.)이있다 다른 이유도 있습니다. 컴파일러는 힌트를 처리하는 방법을 판단하는 다양한 기준을 가질 수 있습니다. 그리고 프로그래머로, 이것은 당신을 의미 한다컴파일러의 측면에 대해 배우거나 결함이있는 아이디어를 바탕으로 결정을 내릴 가능성이 있습니다. 따라서 코드 작성자와 독자 및 코드를 다른 컴파일러로 이식하려는 모든 사람에게 부담이됩니다.

또한 C 및 C ++ 컴파일러는 별도의 컴파일을 지원합니다. 즉, 프로젝트의 다른 관련 코드를 컴파일하지 않고도 C 또는 C ++ 코드를 컴파일 할 수 있습니다. 코드를 인라인하려면 컴파일러가 달리 선택한다고 가정하면 "범위 내에"선언이 있어야 할뿐만 아니라 정의도 있어야합니다. 일반적으로 프로그래머는 '인라인'을 사용하는 경우 이러한 상황이 발생하도록 노력할 것입니다. 그러나 실수가 발생하기 쉽습니다.

일반적으로 적절하다고 생각되는 곳에서 인라인을 사용하지만 의존 할 수 없다고 가정하는 경향이 있습니다. 성능이 중요한 요구 사항이고 OP가 이미 더 "기능적인"경로로 갈 때 상당한 성능 저하가 발생했다고 분명하게 기록한 경우 인라인을 코딩 방식으로 사용하지 않는 것이 좋습니다. 대신 약간 다르지만 완전히 일관된 코드 작성 패턴을 따릅니다.

'인라인'과 별도의 컴파일 단계에서 "범위 내"인 정의에 대한 마지막 참고 사항. 연결 단계에서 작업을 수행 할 수 있습니다 (항상 신뢰할 수있는 것은 아님). 링커가 '인라인'요청에 대해 작동 할 수 있도록 C / C ++ 컴파일러가 오브젝트 파일에 충분한 세부 사항을 묻은 경우에만 발생할 수 있습니다. 개인적으로이 기능을 지원하는 링커 시스템 (Microsoft 외부)은 경험하지 못했습니다. 그러나 발생할 수 있습니다. 다시 말하지만, 의존해야하는지 여부는 상황에 따라 다릅니다. 그러나 나는 좋은 증거를 기반으로 달리 알지 않는 한 링커에 삽질되지 않았다고 생각합니다. 그리고 내가 그것에 의존한다면, 그것은 눈에 띄는 곳에 문서화 될 것입니다.


C ++

관심있는 사람들을 위해 여기에 현재 준비되어 있음에도 불구하고 임베디드 응용 프로그램을 코딩 할 때 C ++에 대해 신중한 이유가 있습니다. 모든 임베디드 C ++ 프로그래머가 차가운 것을 알아야 한다고 생각하는 용어를 던져 보겠습니다 .

  • 부분 템플릿 전문화
  • vtables
  • 가상 기본 개체
  • 활성화 프레임
  • 활성화 프레임 풀기
  • 생성자에서 스마트 포인터 사용 및 이유
  • 반환 값 최적화

그것은 짧은 목록 일뿐입니다. 해당 용어에 대한 모든 정보와 내가 왜 나열했는지 (그리고 여기에 나열하지 않은 많은 것들)를 모르는 경우 프로젝트에 대한 옵션이 아닌 한 임베디드 작업에 C ++을 사용하지 않는 것이 좋습니다. .

C ++ 예외 시맨틱을 간단히 살펴보면 맛을 볼 수 있습니다.

   .
   .
   foo ();
   String s;
   foo ();
   .
   .

C ++ 컴파일러는 foo ()에 대한 첫 번째 호출을보고 foo ()에서 예외가 발생하는 경우 정상적인 활성화 프레임 풀기를 허용 할 수 있습니다. 다시 말해, C ++ 컴파일러는이 시점에서 예외 처리와 관련된 프레임 해제 프로세스를 지원하기 위해 추가 코드가 필요하지 않다는 것을 알고 있습니다.

그러나 일단 String이 생성되면 C ++ 컴파일러는 나중에 예외가 발생할 경우 프레임 해제가 허용되기 전에 올바르게 파괴되어야한다는 것을 알고 있습니다. 따라서 foo ()에 대한 두 번째 호출은 의미 적으로 첫 번째와 다릅니다. foo ()에 대한 두 번째 호출에서 예외가 발생하거나 예외가 발생하는 경우 컴파일러는 일반적인 프레임이 풀리기 전에 String의 소멸을 처리하도록 설계된 코드를 배치해야합니다. 이것은 foo ()에 대한 첫 번째 호출에 필요한 코드 와 다릅니다 .

( 이 문제를 제한하기 위해 C ++에 추가 데코레이션 을 추가 할 수 있습니다 . 그러나 실제로 C ++을 사용하는 프로그래머는 작성하는 각 코드 줄의 의미를 훨씬 더 잘 알고 있어야합니다.)

C의 malloc과 달리 C ++의 새로운 기능은 예외를 사용하여 원시 메모리 할당을 수행 할 수 없을 때 신호를 보냅니다. 'dynamic_cast'도 마찬가지입니다. C ++의 표준 예외에 대해서는 Stroustrup의 3 번째 에디션, C ++ 프로그래밍 언어, 384 및 385 페이지를 참조하십시오. 컴파일러는이 동작을 비활성화 할 수 있습니다. 그러나 일반적으로 예외가 실제로 발생하지 않고 컴파일되는 함수에 실제로 예외 처리 블록이없는 경우에도 생성 된 코드에서 올바르게 처리 된 예외 처리 프롤로그 및 에필로그로 인해 약간의 오버 헤드가 발생합니다. (Stroustrup은 이것을 공개적으로 애도했습니다.)

부분적인 템플릿 전문화 (모든 C ++ 컴파일러에서 지원하지는 않음)가 없으면 템플릿을 사용하면 임베디드 프로그래밍의 재앙이 발생할 수 있습니다. 이것이 없으면 코드 블룸은 심각한 메모리 내장 프로젝트를 플래시로 죽일 수있는 심각한 위험입니다.

C ++ 함수가 객체를 반환하면 명명되지 않은 컴파일러 임시 파일이 만들어지고 소멸됩니다. 일부 C ++ 컴파일러는 객체 생성자가 로컬 객체 대신 return 문에 사용되는 경우 효율적인 코드를 제공하여 하나의 객체로 구성 및 소멸 요구를 줄입니다. 그러나 모든 컴파일러가이 작업을 수행하는 것은 아니며 많은 C ++ 프로그래머는 이러한 "반환 값 최적화"를 인식하지 못합니다.

단일 매개 변수 유형으로 객체 생성자를 제공하면 C ++ 컴파일러가 프로그래머에게 전혀 예상치 못한 방식으로 두 유형 간의 변환 경로를 찾을 수 있습니다. 이런 종류의 "똑똑한"행동은 C의 일부가 아닙니다.

기본 유형을 지정하는 catch 절은 던져진 객체가 객체의 "동적 유형"이 아니라 catch 절의 "정적 유형"을 사용하여 복사되므로 던져진 파생 객체를 "슬라이스"합니다. 예외적 인 불행의 원인은 아닙니다 (내장 코드에서 예외를 감당할 수 있다고 생각 될 때).

C ++ 컴파일러는 의도하지 않은 결과로 생성자, 소멸자, 복사 생성자 및 할당 연산자를 자동으로 생성 할 수 있습니다. 자세한 내용은 시설을 확보하는 데 시간이 걸립니다.

파생 객체의 배열을 기본 객체의 배열을 허용하는 함수에 전달하면 컴파일러 경고가 거의 생성되지 않지만 거의 항상 잘못된 동작이 발생합니다.

C ++는 객체 생성자에서 예외가 발생할 때 부분적으로 생성 된 객체의 소멸자를 호출하지 않기 때문에 생성자에서 예외를 처리하면 예외가 발생하는 경우 생성자의 생성 된 조각이 제대로 파괴되도록하기 위해 일반적으로 "스마트 포인터"가 필요합니다. . (367 페이지 및 368 페이지 Stroustrup 참조) 이는 C ++로 좋은 클래스를 작성하는 데 일반적으로 발생하는 문제이지만 C에는 생성 및 소멸의 시맨틱이 내장되어 있지 않으므로 C에서는 피할 수 있습니다. 객체 내 하위 객체의 의미는 C ++에서이 고유 한 의미 론적 문제에 대처해야하는 코드 작성을 의미합니다. 다시 말해서 C ++ 시맨틱 동작을 "쓰기"입니다.

C ++는 객체 매개 변수에 전달 된 객체를 복사 할 수 있습니다. 예를 들어 다음 조각에서는 "rA (x);" C ++ 컴파일러가 매개 변수 p에 대한 생성자를 호출하도록 할 수 있습니다. 복사 생성자를 호출하여 객체 x를 매개 변수 p로 전송 한 다음 함수 rA의 반환 객체 (명명되지 않은 임시)에 대한 다른 생성자입니다. 파라미터 p에서 복사. 더구나 A 급에 건설이 필요한 자체 물체가 있다면 이것은 망원경으로 재앙을 일으킬 수있다. (AC 프로그래머는 C 프로그래머가 편리한 구문을 가지고 있지 않고 한 번에 하나씩 모든 세부 사항을 표현해야하기 때문에이 쓰레기를 대부분 피하고 수동으로 최적화합니다.)

    class A {...};
    A rA (A p) { return p; }
    // .....
    { A x; rA(x); }

마지막으로 C 프로그래머를위한 짧은 메모입니다. longjmp ()는 C ++에서 이식 가능한 동작이 없습니다. (일부 C 프로그래머는 이것을 일종의 "예외"메커니즘으로 사용합니다.) 일부 C ++ 컴파일러는 실제로 longjmp를 가져올 때 정리하도록 설정하려고 시도하지만 C ++에서는 이러한 동작을 이식 할 수 없습니다. 컴파일러가 생성 된 객체를 정리하면 이식 할 수 없습니다. 컴파일러가 정리하지 않으면 코드가 longjmp의 결과로 생성 된 객체의 범위를 벗어나고 동작이 잘못된 경우 객체가 소멸되지 않습니다. (foo ()에서 longjmp를 사용하여 범위를 벗어나지 않으면 동작이 문제가되지 않을 수 있습니다.) 이것은 C 임베디드 프로그래머가 너무 자주 사용하지는 않지만 사용하기 전에 이러한 문제를 스스로 알아야합니다.


4
한 번만 사용 된 이러한 종류의 함수는 함수 호출로 컴파일되지 않으며 코드는 호출없이 간단히 배치됩니다.
Dorian

6
@Dorian-특정 컴파일러의 경우 특정 상황에서 귀하의 의견이 사실 일 수 있습니다. 함수가 파일 내에서 정적이면 컴파일러는 코드를 인라인으로 만들 수있는 옵션 이 있습니다. 외부에서 볼 수 있다면 실제로 호출되지 않더라도 함수를 호출 할 수있는 방법이 있어야합니다.
uɐɪ

1
@ jonk-좋은 대답에서 언급하지 않은 또 다른 트릭은 확장 인라인 코드로 초기화 또는 구성을 수행하는 간단한 매크로 함수를 작성하는 것입니다. 이것은 RAM / 스택 / 함수 호출 깊이가 제한된 매우 작은 프로세서에서 특히 유용합니다.
uɐɪ

@ ʎyeahʞouɐɪ 예, C에서 매크로에 대해 논의하는 것을 놓쳤습니다. C ++에서는 더 이상 사용되지 않지만 그 점에 대한 논의는 유용 할 수 있습니다. 그것에 대해 쓸만한 것을 알아낼 수 있다면 그것을 해결할 수 있습니다.
jonk

1
@ jonk-나는 당신의 첫 문장에 완전히 동의하지 않습니다. inline static void turnOnFan(void) { PORTAbits &= ~(1<<8); }여러 곳에서 호출 되는 예 는 완벽한 후보입니다.
Jason S

8

1) 가독성 및 유지 관리를위한 코드를 먼저 작성하십시오. 모든 코드베이스의 가장 중요한 측면은 코드가 잘 구성되어 있다는 것입니다. 잘 작성된 소프트웨어는 오류가 적은 경향이 있습니다. 몇 주 / 월 / 년에 변경해야 할 수도 있으며 코드를 읽기 좋으면 크게 도움이됩니다. 또는 다른 사람이 변경해야 할 수도 있습니다.

2) 한 번 실행되는 코드의 성능은별로 중요하지 않습니다. 성능이 아닌 스타일 관리

3) 단단한 루프의 코드조차도 가장 먼저 정확해야합니다. 성능 문제가 발생하면 코드가 정확하면 최적화하십시오.

4) 최적화가 필요한 경우 측정해야합니다! 당신이 있다면 그것은 중요하지 않습니다 생각 또는 누군가가 당신을 알려줍니다static inline컴파일러 그냥 추천입니다. 컴파일러의 기능을 살펴 봐야합니다. 또한 인라인이 성능을 향상 시켰는지 측정해야합니다. 임베디드 시스템에서는 코드 메모리가 일반적으로 매우 제한적이기 때문에 코드 크기도 측정해야합니다. 이것은 엔지니어링과 추측 작업을 구별하는 가장 중요한 규칙입니다. 측정하지 않으면 도움이되지 않습니다. 엔지니어링이 측정 중입니다. 과학은 그것을 적고있다;)


2
내가 당신의 다른 훌륭한 게시물에 대한 유일한 비판은 포인트 2)입니다. 사실이다 성능 하지만 임베디드 환경에서 - 초기화 코드는 무관하다 크기가 중요 할 수 있습니다. (그러나 그것은 포인트 1을 오버라이드하지 않습니다; 당신이 필요로 할 때-그리고 전에는 필요하지 않을 때 크기에 대한 최적화를 시작하십시오)
Martin Bonner는 Monica

2
초기화 코드의 성능은 처음에는 관련이 없을 수 있습니다. 저전력 모드를 추가하고 웨이크 업 이벤트를 처리하기 위해 신속하게 복구하려는 경우 관련성이 높아집니다.
berendi-시위

5

함수가 한 위치에서만 (다른 함수 내에서도) 호출되면 컴파일러는 실제로 함수를 호출하는 대신 항상 코드를 해당 위치에 넣습니다. 함수가 여러 위치에서 호출 된 경우 코드 크기 관점에서 함수를 사용하는 것이 합리적입니다.

코드를 컴파일 한 후에는 여러 번의 호출이 없으므로 가독성이 크게 향상됩니다.

또한 예를 들어 메인 c 파일에없는 다른 ADC 기능을 사용하여 동일한 라이브러리에 ADC 초기화 코드가 있어야합니다.

많은 컴파일러에서 속도 또는 코드 크기에 대해 다른 수준의 최적화를 지정할 수 있으므로 여러 곳에서 호출되는 작은 함수가 있으면 함수가 "인라인"되어 호출 대신 복사됩니다.

속도 최적화는 가능한 한 많은 장소에서 함수를 인라인하고 코드 크기 최적화는 함수를 호출하지만 함수가 한 곳에서만 호출되는 경우 항상 "인라인"됩니다.

다음과 같은 코드 :

function_used_just_once{
   code blah blah;
}
main{
  codeblah;
  function_used_just_once();
  code blah blah blah;
{

다음으로 컴파일됩니다.

main{
 code blah;
 code blah blah;
 code blah blah blah;
}

전화를 사용하지 않고.

그리고 귀하의 질문에 대한 대답, 예를 들어 또는 유사하게 코드의 가독성은 성능에 영향을 미치지 않으며 속도 나 코드 크기가 많지 않습니다. 코드를 읽을 수 있도록 여러 호출을 사용하는 것이 일반적이며 결국 인라인 코드로 준수됩니다.

위의 설명이 Microchip XCxx 무료 버전과 같은 의도적 인 무료 버전 컴파일러에는 유효하지 않도록 지정하기 위해 업데이트하십시오. 이러한 종류의 함수 호출은 Microchip에게 유료 버전의 성능을 보여주는 금광입니다.이 코드를 컴파일하면 C 코드에서와 마찬가지로 ASM에서 정확하게 호출 할 수 있습니다.

또한 인라인 함수에 대한 포인터를 사용할 것으로 예상되는 바보 같은 프로그래머에게는 적합하지 않습니다.

이것은 일반적인 C C ++ 또는 프로그래밍 섹션이 아닌 전자 섹션이며 문제는 괜찮은 컴파일러가 기본적으로 위의 최적화를 수행하는 마이크로 컨트롤러 프로그래밍에 관한 것입니다.

드문 드문 경우지만 사실이 아닐 수 있으므로 다운 베이트를 중지하십시오.


15
코드가 인라인인지 여부는 컴파일러 공급 업체 구현 관련 문제입니다. 인라인 키워드를 사용해도 인라인 코드를 보장 하지는 않습니다 . 컴파일러에 대한 힌트입니다. 확실히 좋은 컴파일러는 알고있는 함수를 한 번만 인라인 할 것입니다. 그러나 범위에 "휘발성"개체가있는 경우 일반적으로 그렇게하지 않습니다.
피터 스미스

9
이 답변은 사실이 아닙니다. @PeterSmith가 말했듯이 C 언어 사양에 따르면 컴파일러는 코드를 인라인하는 옵션 이 있지만 그렇지 않을 수도 있으며 많은 경우 그렇게하지 않습니다. 이 답변에 담요 선언을 작성하고 모든 컴파일러가 옵션을 가질 수 없을 때만 코드를 인라인으로 배치한다고 가정하면 너무 많은 다른 대상 프로세서에 대해 너무 많은 컴파일러가 있습니다.
uɐɪ

2
@ ʎəʞouɐɪ 당신은 불가능한 드문 경우를 가리키고 있으며 처음에는 함수를 호출하지 않는 것이 좋지 않습니다. 나는 OP가 제공 한 간단한 예제에서 실제로 호출을 사용하는 바보 같은 컴파일러를 본 적이 없다.
Dorian

6
이러한 함수가 한 번만 호출되는 경우 함수 호출 최적화는 거의 문제가되지 않습니다. 설정하는 동안 시스템은 매 클럭 사이클마다 클로킹해야합니까? 어디서나 읽을 수있는 코드를 최적화하는 경우와 마찬가지로 프로파일 링에 필요한 것으로 표시되는 경우에만 최적화하십시오 .
Baldrickk

5
@MSalters 프로그래머가 컴파일러에 접근하는 방식에 대해 컴파일러가 여기서하는 일에 관심이 없습니다. 질문에서 볼 수 있듯이 초기화를 깨 뜨리면 성능이 전혀 또는 무시할 수 없습니다.
Baldrickk

2

우선, 최고 또는 최악은 없습니다. 그것은 모두 의견의 문제입니다. 이것이 비효율적이라는 것은 매우 정확합니다. 최적화 될 수도 있고 그렇지 않을 수도 있습니다. 때에 따라 다르지. 일반적으로 이러한 유형의 기능, 시계, GPIO, 타이머 등은 별도의 파일 / 디렉토리에 표시됩니다. 컴파일러는 일반적으로 이러한 간격에서 최적화 할 수 없었습니다. 내가 알 수는 있지만 이와 같은 것들에 널리 사용되지 않는 것이 있습니다.

단일 파일:

void dummy (unsigned int);

void setCLK()
{
    // Code to set the clock
    dummy(5);
}

void setConfig()
{
    // Code to set the configuration
    dummy(6);
}

void setSomethingElse()
{
   // 1 line code to write something to a register.
    dummy(7);
}

void initModule()
{
   setCLK();
   setConfig();
   setSomethingElse();
}

데모 목적으로 대상 및 컴파일러 선택

Disassembly of section .text:

00000000 <setCLK>:
   0:    e92d4010     push    {r4, lr}
   4:    e3a00005     mov    r0, #5
   8:    ebfffffe     bl    0 <dummy>
   c:    e8bd4010     pop    {r4, lr}
  10:    e12fff1e     bx    lr

00000014 <setConfig>:
  14:    e92d4010     push    {r4, lr}
  18:    e3a00006     mov    r0, #6
  1c:    ebfffffe     bl    0 <dummy>
  20:    e8bd4010     pop    {r4, lr}
  24:    e12fff1e     bx    lr

00000028 <setSomethingElse>:
  28:    e92d4010     push    {r4, lr}
  2c:    e3a00007     mov    r0, #7
  30:    ebfffffe     bl    0 <dummy>
  34:    e8bd4010     pop    {r4, lr}
  38:    e12fff1e     bx    lr

0000003c <initModule>:
  3c:    e92d4010     push    {r4, lr}
  40:    e3a00005     mov    r0, #5
  44:    ebfffffe     bl    0 <dummy>
  48:    e3a00006     mov    r0, #6
  4c:    ebfffffe     bl    0 <dummy>
  50:    e3a00007     mov    r0, #7
  54:    ebfffffe     bl    0 <dummy>
  58:    e8bd4010     pop    {r4, lr}
  5c:    e12fff1e     bx    lr

이것은 대부분의 대답이 당신에게 말하고있는 것입니다. 당신은 순진하고 이것이 모두 최적화되고 기능이 제거된다는 것입니다. 글쎄, 그것들은 기본적으로 전역 적으로 정의되어 있기 때문에 제거되지 않습니다. 이 파일 외부에서 필요하지 않은 경우 제거 할 수 있습니다.

void dummy (unsigned int);

static void setCLK()
{
    // Code to set the clock
    dummy(5);
}

static void setConfig()
{
    // Code to set the configuration
    dummy(6);
}

static void setSomethingElse()
{
   // 1 line code to write something to a register.
    dummy(7);
}

void initModule()
{
   setCLK();
   setConfig();
   setSomethingElse();
}

인라인 상태로 지금 제거합니다.

Disassembly of section .text:

00000000 <initModule>:
   0:    e92d4010     push    {r4, lr}
   4:    e3a00005     mov    r0, #5
   8:    ebfffffe     bl    0 <dummy>
   c:    e3a00006     mov    r0, #6
  10:    ebfffffe     bl    0 <dummy>
  14:    e3a00007     mov    r0, #7
  18:    ebfffffe     bl    0 <dummy>
  1c:    e8bd4010     pop    {r4, lr}
  20:    e12fff1e     bx    lr

하지만 실제로는 칩 공급 업체 나 BSP 라이브러리를 사용하는 경우

Disassembly of section .text:

00000000 <_start>:
   0:    e3a0d902     mov    sp, #32768    ; 0x8000
   4:    eb000010     bl    4c <initModule>
   8:    eafffffe     b    8 <_start+0x8>

0000000c <dummy>:
   c:    e12fff1e     bx    lr

00000010 <setCLK>:
  10:    e92d4010     push    {r4, lr}
  14:    e3a00005     mov    r0, #5
  18:    ebfffffb     bl    c <dummy>
  1c:    e8bd4010     pop    {r4, lr}
  20:    e12fff1e     bx    lr

00000024 <setConfig>:
  24:    e92d4010     push    {r4, lr}
  28:    e3a00006     mov    r0, #6
  2c:    ebfffff6     bl    c <dummy>
  30:    e8bd4010     pop    {r4, lr}
  34:    e12fff1e     bx    lr

00000038 <setSomethingElse>:
  38:    e92d4010     push    {r4, lr}
  3c:    e3a00007     mov    r0, #7
  40:    ebfffff1     bl    c <dummy>
  44:    e8bd4010     pop    {r4, lr}
  48:    e12fff1e     bx    lr

0000004c <initModule>:
  4c:    e92d4010     push    {r4, lr}
  50:    ebffffee     bl    10 <setCLK>
  54:    ebfffff2     bl    24 <setConfig>
  58:    ebfffff6     bl    38 <setSomethingElse>
  5c:    e8bd4010     pop    {r4, lr}
  60:    e12fff1e     bx    lr

성능과 공간에 현저한 비용이 드는 오버 헤드를 추가하기 시작하는 것이 가장 확실합니다. 각 기능이 얼마나 작은 지에 따라 각각의 5 ~ 5 %.

어쨌든 이것이 왜 이루어 집니까? 그 중 일부는 교수들이 채점 코드를 더 쉽게 만들기 위해 가르치거나 가르치는 일련의 규칙입니다. 함수는 페이지에 맞아야합니다 (종이에 인쇄 할 때 다시).이 작업을 수행하지 마십시오. 그렇게하지 마십시오. 많은 다른 대상에 대해 공통 이름을 가진 라이브러리를 만드는 것입니다. 주변기기를 공유하고 일부는 주변기기를 공유하지 않는 수십 개의 마이크로 컨트롤러 제품군이있는 경우, 제품군, 다른 GPIO, SPI 컨트롤러 등에서 혼합 된 3-4 개의 다른 UART 특징이있을 수 있습니다. 일반적인 gpio_init () 함수를 사용할 수 있습니다. get_timer_count () 등. 다른 주변 장치에 해당 추상화를 재사용하십시오.

대부분의 가독성과 소프트웨어 디자인의 경우가 될 수 있습니다. 유지 보수성, 가독성 및 성능 모두를 가질 수는 없습니다. 한 번에 하나 또는 두 개만 선택할 수 있습니다.

이것은 매우 의견에 기반한 질문이며, 위의 3 가지 주요 방법이 나와 있습니다. 어떤 경로가 최선인지에 대해서는 엄격하게 의견입니다. 하나의 기능으로 모든 작업을 수행하고 있습니까? 의견에 기반한 질문, 일부 사람들은 성능에 의존하고 일부는 모듈 성과 해당 가독성 버전을 BEST로 정의합니다. 많은 사람들이 가독성이라고 부르는 흥미로운 문제는 매우 고통 스럽습니다. 50 ~ 10,000 개의 파일을 한 번에 열어야하는 코드를 "보아"려면 어떻게되는지 실행 순서로 함수를 선형으로 보려고합니다. 가독성의 반대라는 것을 알지만, 다른 항목은 각 항목이 화면 / 편집기 창에 맞으면 읽을 수 있으며, 호출되는 기능을 암기 한 후 및 / 또는 튀어 나올 수있는 편집기가 있으면 전체를 사용할 수 있습니다. 프로젝트 내의 각 기능.

다양한 솔루션을 볼 때 또 다른 큰 요소입니다. 텍스트 편집기, IDE 등은 매우 개인적이며 vi 대 Emacs를 뛰어 넘습니다. 사용중인 도구에 익숙하고 효율적이면 프로그래밍 효율성, 하루 / 월 라인이 증가합니다. 도구의 기능은 의도적으로 또는 도구의 팬이 코드를 작성하는 방식에 의존 할 수 있습니다. 결과적으로 한 개인이 이러한 라이브러리를 작성하는 경우 프로젝트는 어느 정도 이러한 습관을 반영합니다. 팀 임에도 불구하고 수석 개발자 또는 상사의 습관 / 선호도는 팀의 나머지 팀에 강제 될 수 있습니다.

매우 개인적인 vi, Emacs, 탭과 공백, 대괄호 정렬 방법 등 다양한 개인 취향을 가진 코딩 표준이 있습니다. 그리고 이것은 라이브러리가 어느 정도 디자인되었는지에 영향을줍니다.

어떻게 작성해야합니까? 그러나 원하는 경우 실제로 작동하면 잘못된 대답이 없습니다. 잘못되었거나 위험한 코드는 확실하지만 필요에 따라 유지할 수 있도록 작성된 코드는 설계 목표를 충족하고 성능이 중요한 경우 가독성 및 일부 유지 관리 성을 포기하며 그 반대도 마찬가지입니다. 한 줄의 코드가 편집기 창의 너비에 맞도록 짧은 변수 이름을 좋아합니까? 혼동을 피하기 위해 지나치게 지나치게 서술적인 이름이지만 페이지에서 한 줄을 얻을 수 없기 때문에 가독성이 떨어집니다. 이제 흐름이 엉망이되어 시각적으로 깨졌습니다.

타석에서 처음으로 홈런을 치지 않을 것입니다. 스타일을 진정으로 정의하는 데 수십 년이 걸릴 수 있습니다. 그와 동시에 그 시간 동안 스타일이 바뀌어 한 방향으로 기울어지고 다른 방향으로 기울어 질 수 있습니다.

최적화하지 않음, 절대 최적화하지 않음 및 조기 최적화를 많이 듣게 될 것입니다. 그러나 그림과 같이 처음부터 이와 같은 디자인은 성능 문제를 일으킨 후 처음부터 다시 디자인하기보다는 해킹을 통해 해당 문제를 해결하기 시작합니다. 컴파일러가 수행하려는 작업에 대한 두려움에 기초하여 컴파일러를 조작하기 위해 노력할 수있는 몇 가지 코드 행의 상황이 하나 있음에 동의합니다 (이러한 코딩 경험은 쉽고 자연 스럽습니다. 컴파일러가 코드를 컴파일하는 방법을 알고 쓸 때 최적화), 공격하기 전에 사이클 스틸러가 실제로 어디에 있는지 확인하고 싶습니다.

또한 사용자를 위해 코드를 어느 정도 디자인해야합니다. 이것이 당신의 프로젝트라면 당신은 유일한 개발자입니다; 그것은 당신이 원하는 것입니다. 라이브러리를 제공하거나 판매하려는 경우 코드를 다른 모든 라이브러리, 작은 함수, 긴 함수 이름 및 긴 변수 이름을 가진 수백에서 수천 개의 파일처럼 보이게 할 수 있습니다. 가독성 문제와 성능 문제에도 불구하고 IMO를 사용하면 더 많은 사람들이 해당 코드를 사용할 수 있습니다.


4
정말? "어떤 대상"과 "어떤 컴파일러"를 사용하겠습니까?
도리안

32/64 비트 ARM8과 비슷하게 보입니다. 어쩌면 라스베리 파이에서 일반적인 마이크로 컨트롤러입니다. 질문의 첫 문장을 읽었습니까?
도리안

컴파일러는 사용하지 않는 전역 함수를 제거하지는 않지만 링커는 제거합니다. 올바르게 구성되어 사용되면 실행 파일에 표시되지 않습니다.
berendi-시위

누군가 어떤 파일 간격에서 어떤 컴파일러를 최적화 할 수 있는지 궁금해하는 경우 IAR 컴파일러는 다중 파일 컴파일 (즉,이를 호출하는 방식)을 지원하므로 파일 간 최적화가 가능합니다. 모든 c / cpp 파일을 한 번에 던져 넣으면 main이라는 단일 기능을 포함하는 실행 파일이 생깁니다. 성능상의 이점은 상당히 심할 수 있습니다.
아스날

3
@Arsenal 물론 gcc는 적절하게 호출되면 컴파일 단위에서도 인라인을 지원합니다. gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html을 참조 하고 -flto 옵션을 찾으십시오.
피터-복원 모니카

1

매우 일반적인 규칙-컴파일러는 사용자보다 더 잘 최적화 할 수 있습니다. 물론 루프 집약적 인 작업을 수행하는 경우 예외가 있지만 속도 또는 코드 크기에 대한 좋은 최적화를 원한다면 컴파일러를 현명하게 선택하십시오.


안타깝게도 오늘날 대부분의 프로그래머에게는 사실입니다.
도리안

0

그것은 자신의 코딩 스타일에 달려 있습니다. 거기에 하나의 일반적인 규칙은, 함수 이름뿐만 아니라 변수 이름은 가능한 한 명확하고 스스로 설명해야한다는 것입니다. 함수에 더 많은 하위 호출이나 코드 라인을 넣으면 해당 함수에 대한 명확한 작업을 정의하기가 더 어려워집니다. 귀하의 예에는 물건 initModule()초기화 하고 서브 루틴을 호출 하여 시계설정 하거나 구성을 설정하는 기능이 있습니다 . 함수 이름 만 읽으면 알 수 있습니다. 서브 루틴의 모든 코드를 initModule()직접 넣으면 함수가 실제로하는 일이 덜 명확 해집니다. 그러나 종종 가이드 라인 일뿐입니다.


당신의 답변에 감사드립니다. 성능에 필요한 경우 스타일을 변경할 수 있지만 여기서 질문은 코드의 가독성이 성능에 영향을 줍니까?
MaNyYaCk

함수 호출로 인해 호출 또는 jmp 명령이 발생하지만 이는 제 생각에는 무시할만한 자원의 희생입니다. 디자인 패턴을 사용하는 경우 실제 코드 스니핑에 도달하기 전에 수십 층의 함수 호출로 끝나는 경우가 있습니다.
po.pe

@Humpawumpa은 - 당신이 다음 RAM 함수 호출의 12 층의 256 또는 64 바이트 마이크로 컨트롤러에 대해 작성하는 경우하면 그냥 불가능, 무시할 희생하지 않습니다
uɐɪ

그렇습니다. 그러나 이것은 두 가지 극단입니다 ... 보통 256 바이트가 넘고 12 개 미만의 레이어를 사용하고 있습니다.
po.pe

0

함수가 실제로 아주 작은 것을 하나만 수행하는 경우이를 고려하십시오 static inline.

C 파일 대신 헤더 파일에 추가하고 단어 static inline를 사용하여 정의하십시오.

static inline void setCLK()
{
    //code to set the clock
}

이제 3 줄 이상과 같이 함수가 약간 더 길면이를 피하고 static inline.c 파일에 추가 하는 것이 좋습니다 . 결국 임베디드 시스템에는 메모리가 제한되어 있으므로 코드 크기를 너무 늘리고 싶지 않습니다.

또한 함수를 정의하고에서 함수를 file1.c사용 file2.c하면 컴파일러가 자동으로 인라인하지 않습니다. 그러나 함수 file1.h로 정의하면 static inline컴파일러가 인라인 할 가능성이 있습니다.

static inline기능은 고성능 프로그래밍에 매우 유용합니다. 코드 성능을 종종 세 배 이상 향상시키는 것으로 나타났습니다.


"예를 들어 3 줄 이상"-줄 수는 그와 관련이 없습니다. 인라인 비용은 그것과 관련이 있습니다. 인라인에 완벽한 20 줄 함수와 인라인에 끔찍한 3 줄 함수 (예 : functionB ()를 3 번 ​​호출하는 functionA (), functionC ()를 3 번 ​​호출하는 functionB ())를 작성할 수 있습니다. 다른 몇 가지 수준).
Jason S

또한 함수를 정의하고에서 함수를 file1.c사용 file2.c하면 컴파일러가 자동으로 인라인하지 않습니다. 거짓 . 예를 들어 -fltogcc 또는 clang을 참조하십시오 .
berendi-시위

0

마이크로 컨트롤러를위한 효율적이고 안정적인 코드를 작성하는 데 어려움이있는 한 가지는 코드가 컴파일러 관련 지시문을 사용하거나 많은 최적화를 비활성화하지 않으면 일부 컴파일러가 특정 의미를 안정적으로 처리 할 수 ​​없다는 것입니다.

예를 들어, 인터럽트 서비스 루틴 [타이머 틱 등으로 실행]이있는 단일 코어 시스템이있는 경우 :

volatile uint32_t *magic_write_ptr,magic_write_count;
void handle_interrupt(void)
{
  if (magic_write_count)
  {
    magic_write_count--;
    send_data(*magic_write_ptr++)
  }
}

백그라운드 쓰기 작업을 시작하거나 완료 될 때까지 기다리는 함수를 작성할 수 있어야합니다.

void wait_for_background_write(void)
{
  while(magic_write_count)
    ;
}
void start_background_write(uint32_t *dat, uint32_t count)
{
  wait_for_background_write();
  background_write_ptr = dat;
  background_write_count = count;
}

그런 다음 다음을 사용하여 해당 코드를 호출하십시오.

uint32_t buff[16];

... write first set of data into buff
start_background_write(buff, 16);
... do some stuff unrelated to buff
wait_for_background_write();

... write second set of data into buff
start_background_write(buff, 16);
... etc.

불행히도, 전체 최적화를 사용하면 gcc 또는 clang과 같은 "영리한"컴파일러는 첫 번째 쓰기 세트가 프로그램의 관찰 가능에 영향을 줄 수 없으므로 최적화 할 수있는 방법이 없다고 결정합니다. icc인터럽트를 설정하고 완료를 기다리는 동작에 휘발성 쓰기와 휘발성 읽기가 모두 포함되어 있지만 (여기에서 와 같이) 퀄리티 컴파일러 는이 작업을 수행하기가 쉽지 않습니다.icc 가 쉽지 않습니다.

이 표준은 위의 구성을 처리 할 수있는 몇 가지 합리적인 방법이 있음을 확인하면서 구현 품질 문제를 의도적으로 무시합니다.

  1. 하이 엔드 번호 크 런칭과 같은 필드 전용 의 품질 구현 은 그러한 필드에 대해 작성된 코드에 위와 같은 구문이 포함되지 않을 것으로 예상 할 수 있습니다.

  2. 품질 구현은 volatile외부 세계에 보이는 모든 객체에 액세스하는 작업을 트리거 할 수있는 것처럼 객체에 대한 모든 액세스를 처리 할 수 있습니다.

  3. 임베디드 시스템 용으로 설계된 단순하지만 적절한 품질 구현은 "인라인"으로 표시되지 않은 함수에 대한 모든 호출을 외부 세계에 노출 된 객체에 액세스 할 수있는 것처럼 처리 할 수 ​​있습니다 volatile. 2.

표준은 위의 접근법 중 어느 것이 품질 구현에 가장 적합한 지 제안하거나, "적합한"구현이 특정 목적에 사용하기에 충분한 품질을 요구하지는 않는다. 따라서 gcc 또는 clang과 같은 일부 컴파일러는이 패턴을 사용하려는 모든 코드를 많은 최적화를 비활성화 한 상태에서 컴파일해야합니다.

경우에 따라 I / O 함수가 별도의 컴파일 단위에 있고 컴파일러가 외부 세계에 노출 된 객체의 임의의 하위 집합에 액세스 할 수 있다고 가정하는 것 외에는 선택의 여지가없는 것이 합리적 일 수 있습니다. gcc 및 clang과 안정적으로 작동하는 코드 작성 방법을 공개합니다. 그러나 이러한 경우 목표는 불필요하게 함수 호출의 추가 비용을 피하는 것이 아니라 필요한 의미를 얻는 대신 불필요한 비용을 수용하는 것입니다.


"I / O 함수가 별도의 컴파일 단위에 있는지 확인"은 이러한 최적화 문제를 방지하는 확실한 방법이 아닙니다. 적어도 LLVM과 나는 GCC가 많은 경우에 전체 프로그램 최적화를 수행한다고 생각하므로 IO 함수가 별도의 컴파일 단위에 있더라도 IO 함수를 인라인하기로 결정할 수 있습니다.
Jules

@Jules : 모든 구현이 내장 소프트웨어 작성에 적합한 것은 아닙니다. 전체 프로그램 최적화를 비활성화하면 gcc 또는 clang이 해당 목적에 적합한 품질 구현으로 작동하도록하는 가장 저렴한 방법 일 수 있습니다.
supercat

@Jules : 임베디드 또는 시스템 프로그래밍을위한 고품질 구현은 전체 프로그램 최적화를 완전히 비활성화하지 않고도 해당 목적에 적합한 의미를 갖도록 구성 할 수 있어야합니다 (예 : volatile액세스를 잠재적으로 트리거 할 수있는 것처럼 액세스 를 처리하는 옵션을 가짐) gcc와 clang은 어떤 이유로 든 구현 품질 문제를 쓸모없는 방식으로 초대하는 것으로 간주합니다.
supercat

1
"최고 품질"구현조차도 버그가있는 코드를 수정하지는 않습니다. buff선언되지 않은 경우 volatile변수는 휘발성 변수로 취급되지 않으며, 나중에 사용하지 않는 경우 해당 변수에 대한 액세스가 재정렬되거나 완전히 최적화 될 수 있습니다. 규칙은 간단합니다. 일반 프로그램 흐름 외부에서 액세스 할 수있는 모든 변수 (컴파일러에서 볼 수 있음)를로 표시하십시오 volatile. buff인터럽트 처리기에서 내용이 액세스됩니까? 예. 그런 다음이어야합니다 volatile.
berendi-시위

@berendi : 컴파일러는 표준이 요구하는 것 이상의 품질 보증을 제공 할 수 있으며 품질 컴파일러는 그렇게 할 수 있습니다. 임베디드 시스템 사용을위한 양질의 독립형 구현은 프로그래머가 뮤텍스 구조를 합성 할 수있게 해줄 것이며, 이는 본질적으로 코드가하는 일입니다. 때 magic_write_count제로, 저장은 메인 라인으로 소유하고 있습니다. 0이 아닌 경우 인터럽트 핸들러가 소유합니다. 만들기 buff휘발성은 필요 그것이 사용시 작동 모든 기능 어디서나 volatile훨씬 더 많은 컴파일러를하는 것보다 최적화를 손상 할 restrict로 포인터를 ...
supercat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.