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

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

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답변에 정의 된 가독성을 고려하십시오 .



답변

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

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

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

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


답변

한 줄의 코드를 함수 또는 서브 루틴으로 감싸서 내가 생각할 수있는 이점은 없습니다 (그러나 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 임베디드 프로그래머가 너무 자주 사용하지는 않지만 사용하기 전에 이러한 문제를 스스로 알아야합니다.


답변

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

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

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

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


답변

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

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

또한 예를 들어 메인 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 ++ 또는 프로그래밍 섹션이 아닌 전자 섹션이며 문제는 괜찮은 컴파일러가 기본적으로 위의 최적화를 수행하는 마이크로 컨트롤러 프로그래밍에 관한 것입니다.

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


답변

우선, 최고 또는 최악은 없습니다. 그것은 모두 의견의 문제입니다. 이것이 비효율적이라는 것은 매우 정확합니다. 최적화 될 수도 있고 그렇지 않을 수도 있습니다. 때에 따라 다르지. 일반적으로 이러한 유형의 기능, 시계, 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를 사용하면 더 많은 사람들이 해당 코드를 사용할 수 있습니다.


답변

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


답변

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